You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

385 line
8.8 KiB

  1. package storage
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "log"
  9. "net/http"
  10. "os"
  11. "path/filepath"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "golang.org/x/oauth2"
  16. "golang.org/x/oauth2/google"
  17. "google.golang.org/api/drive/v3"
  18. "google.golang.org/api/googleapi"
  19. )
  20. const GDriveRootConfigFile = "root_id.conf"
  21. const GDriveTokenJsonFile = "token.json"
  22. const GDriveDirectoryMimeType = "application/vnd.google-apps.folder"
  23. type GDrive struct {
  24. service *drive.Service
  25. rootId string
  26. basedir string
  27. localConfigPath string
  28. chunkSize int
  29. logger *log.Logger
  30. }
  31. func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir string, chunkSize int, logger *log.Logger) (*GDrive, error) {
  32. b, err := ioutil.ReadFile(clientJsonFilepath)
  33. if err != nil {
  34. return nil, err
  35. }
  36. // If modifying these scopes, delete your previously saved client_secret.json.
  37. config, err := google.ConfigFromJSON(b, drive.DriveScope, drive.DriveMetadataScope)
  38. if err != nil {
  39. return nil, err
  40. }
  41. srv, err := drive.New(getGDriveClient(config, localConfigPath, logger))
  42. if err != nil {
  43. return nil, err
  44. }
  45. chunkSize = chunkSize * 1024 * 1024
  46. storage := &GDrive{service: srv, basedir: basedir, rootId: "", localConfigPath: localConfigPath, chunkSize: chunkSize, logger: logger}
  47. err = storage.setupRoot()
  48. if err != nil {
  49. return nil, err
  50. }
  51. return storage, nil
  52. }
  53. func (s *GDrive) Type() string {
  54. return "gdrive"
  55. }
  56. func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, metadata Metadata, err error) {
  57. var fileId string
  58. fileId, err = s.findId(filename, token)
  59. if err != nil {
  60. return
  61. }
  62. var fi *drive.File
  63. fi, err = s.service.Files.Get(fileId).Do()
  64. if !s.hasChecksum(fi) {
  65. err = fmt.Errorf("Cannot find file %s/%s", token, filename)
  66. return
  67. }
  68. if err != nil {
  69. return nil, Metadata{}, err
  70. }
  71. downloads, err := strconv.Atoi(fi.Properties["downloads"])
  72. if err != nil {
  73. return nil, Metadata{}, err
  74. }
  75. maxdownloads, err := strconv.Atoi(fi.Properties["maxDownloads"])
  76. if err != nil {
  77. return nil, Metadata{}, err
  78. }
  79. expires, err := time.Parse("2020-02-02 02:02:02", fi.Properties["expires"])
  80. if err != nil {
  81. return nil, Metadata{}, err
  82. }
  83. metadata = Metadata{
  84. ContentType: "",
  85. ContentLength: fi.Size,
  86. Downloads: downloads,
  87. MaxDownloads: maxdownloads,
  88. MaxDate: expires,
  89. DeletionToken: fi.Properties["deletionToken"],
  90. Secret: fi.Properties["deletionSecret"],
  91. }
  92. ctx := context.Background()
  93. var res *http.Response
  94. res, err = s.service.Files.Get(fileId).Context(ctx).Download()
  95. if err != nil {
  96. return
  97. }
  98. reader = res.Body
  99. return
  100. }
  101. func (s *GDrive) Head(token string, filename string) (metadata Metadata, err error) {
  102. var fileId string
  103. fileId, err = s.findId(filename, token)
  104. if err != nil {
  105. return
  106. }
  107. var fi *drive.File
  108. if fi, err = s.service.Files.Get(fileId).Do(); err != nil {
  109. return
  110. }
  111. downloads, err := strconv.Atoi(fi.Properties["downloads"])
  112. if err != nil {
  113. return Metadata{}, err
  114. }
  115. maxdownloads, err := strconv.Atoi(fi.Properties["maxDownloads"])
  116. if err != nil {
  117. return Metadata{}, err
  118. }
  119. expires, err := time.Parse("2020-02-02 02:02:02", fi.Properties["expires"])
  120. if err != nil {
  121. return Metadata{}, err
  122. }
  123. metadata = Metadata{
  124. ContentType: "",
  125. ContentLength: fi.Size,
  126. Downloads: downloads,
  127. MaxDownloads: maxdownloads,
  128. MaxDate: expires,
  129. DeletionToken: fi.Properties["deletionToken"],
  130. Secret: fi.Properties["deletionSecret"],
  131. }
  132. return
  133. }
  134. func (s *GDrive) Meta(token string, filename string, metadata Metadata) error {
  135. return nil
  136. }
  137. func (s *GDrive) Put(token string, filename string, reader io.Reader, metadata Metadata) error {
  138. dirId, err := s.findId("", token)
  139. if err != nil {
  140. return err
  141. }
  142. if dirId == "" {
  143. dir := &drive.File{
  144. Name: token,
  145. Parents: []string{s.rootId},
  146. MimeType: GDriveDirectoryMimeType,
  147. }
  148. di, err := s.service.Files.Create(dir).Fields("id").Do()
  149. if err != nil {
  150. return err
  151. }
  152. dirId = di.Id
  153. }
  154. // Instantiate empty drive file
  155. dst := &drive.File{
  156. Name: filename,
  157. Parents: []string{dirId},
  158. MimeType: metadata.ContentType,
  159. Properties: map[string]string{
  160. "downloads": strconv.Itoa(metadata.Downloads),
  161. "maxDownloads": strconv.Itoa(metadata.MaxDownloads),
  162. "deletionToken": metadata.DeletionToken,
  163. "deletionSecret": metadata.Secret,
  164. "expires": metadata.MaxDate.String(),
  165. },
  166. }
  167. ctx := context.Background()
  168. _, err = s.service.Files.Create(dst).Context(ctx).Media(reader, googleapi.ChunkSize(s.chunkSize)).Do()
  169. if err != nil {
  170. return err
  171. }
  172. return nil
  173. }
  174. func (s *GDrive) Delete(token string, filename string) (err error) {
  175. metadata, _ := s.findId(fmt.Sprintf("%s.metadata", filename), token)
  176. s.service.Files.Delete(metadata).Do()
  177. var fileId string
  178. fileId, err = s.findId(filename, token)
  179. if err != nil {
  180. return
  181. }
  182. err = s.service.Files.Delete(fileId).Do()
  183. return
  184. }
  185. func (s *GDrive) IsNotExist(err error) bool {
  186. if err != nil {
  187. if e, ok := err.(*googleapi.Error); ok {
  188. return e.Code == http.StatusNotFound
  189. }
  190. }
  191. return false
  192. }
  193. func (s *GDrive) DeleteExpired() error {
  194. return nil
  195. }
  196. func (s *GDrive) setupRoot() error {
  197. rootFileConfig := filepath.Join(s.localConfigPath, GDriveRootConfigFile)
  198. rootId, err := ioutil.ReadFile(rootFileConfig)
  199. if err != nil && !os.IsNotExist(err) {
  200. return err
  201. }
  202. if string(rootId) != "" {
  203. s.rootId = string(rootId)
  204. return nil
  205. }
  206. dir := &drive.File{
  207. Name: s.basedir,
  208. MimeType: GDriveDirectoryMimeType,
  209. }
  210. di, err := s.service.Files.Create(dir).Fields("id").Do()
  211. if err != nil {
  212. return err
  213. }
  214. s.rootId = di.Id
  215. err = ioutil.WriteFile(rootFileConfig, []byte(s.rootId), os.FileMode(0600))
  216. if err != nil {
  217. return err
  218. }
  219. return nil
  220. }
  221. func (s *GDrive) hasChecksum(f *drive.File) bool {
  222. return f.Md5Checksum != ""
  223. }
  224. func (s *GDrive) list(nextPageToken string, q string) (*drive.FileList, error) {
  225. return s.service.Files.List().Fields("nextPageToken, files(id, name, mimeType)").Q(q).PageToken(nextPageToken).Do()
  226. }
  227. func (s *GDrive) findId(filename string, token string) (string, error) {
  228. filename = strings.Replace(filename, `'`, `\'`, -1)
  229. filename = strings.Replace(filename, `"`, `\"`, -1)
  230. fileId, tokenId, nextPageToken := "", "", ""
  231. q := fmt.Sprintf("'%s' in parents and name='%s' and mimeType='%s' and trashed=false", s.rootId, token, GDriveDirectoryMimeType)
  232. l, err := s.list(nextPageToken, q)
  233. if err != nil {
  234. return "", err
  235. }
  236. for 0 < len(l.Files) {
  237. for _, fi := range l.Files {
  238. tokenId = fi.Id
  239. break
  240. }
  241. if l.NextPageToken == "" {
  242. break
  243. }
  244. l, err = s.list(l.NextPageToken, q)
  245. }
  246. if filename == "" {
  247. return tokenId, nil
  248. } else if tokenId == "" {
  249. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  250. }
  251. q = fmt.Sprintf("'%s' in parents and name='%s' and mimeType!='%s' and trashed=false", tokenId, filename, GDriveDirectoryMimeType)
  252. l, err = s.list(nextPageToken, q)
  253. if err != nil {
  254. return "", err
  255. }
  256. for 0 < len(l.Files) {
  257. for _, fi := range l.Files {
  258. fileId = fi.Id
  259. break
  260. }
  261. if l.NextPageToken == "" {
  262. break
  263. }
  264. l, err = s.list(l.NextPageToken, q)
  265. }
  266. if fileId == "" {
  267. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  268. }
  269. return fileId, nil
  270. }
  271. // Retrieve a token, saves the token, then returns the generated client.
  272. func getGDriveClient(config *oauth2.Config, localConfigPath string, logger *log.Logger) *http.Client {
  273. tokenFile := filepath.Join(localConfigPath, GDriveTokenJsonFile)
  274. tok, err := gDriveTokenFromFile(tokenFile)
  275. if err != nil {
  276. tok = getGDriveTokenFromWeb(config, logger)
  277. saveGDriveToken(tokenFile, tok, logger)
  278. }
  279. return config.Client(context.Background(), tok)
  280. }
  281. // Request a token from the web, then returns the retrieved token.
  282. func getGDriveTokenFromWeb(config *oauth2.Config, logger *log.Logger) *oauth2.Token {
  283. authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
  284. fmt.Printf("Go to the following link in your browser then type the "+
  285. "authorization code: \n%v\n", authURL)
  286. var authCode string
  287. if _, err := fmt.Scan(&authCode); err != nil {
  288. logger.Fatalf("Unable to read authorization code %v", err)
  289. }
  290. tok, err := config.Exchange(context.TODO(), authCode)
  291. if err != nil {
  292. logger.Fatalf("Unable to retrieve token from web %v", err)
  293. }
  294. return tok
  295. }
  296. // Retrieves a token from a local file.
  297. func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
  298. f, err := os.Open(file)
  299. defer f.Close()
  300. if err != nil {
  301. return nil, err
  302. }
  303. tok := &oauth2.Token{}
  304. err = json.NewDecoder(f).Decode(tok)
  305. return tok, err
  306. }
  307. // Saves a token to a file path.
  308. func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
  309. logger.Printf("Saving credential file to: %s\n", path)
  310. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  311. defer f.Close()
  312. if err != nil {
  313. logger.Fatalf("Unable to cache oauth token: %v", err)
  314. }
  315. json.NewEncoder(f).Encode(token)
  316. }