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.
 
 
 

386 lines
8.9 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. //ToDo: figure out if necessary
  195. return nil
  196. }
  197. func (s *GDrive) setupRoot() error {
  198. rootFileConfig := filepath.Join(s.localConfigPath, GDriveRootConfigFile)
  199. rootId, err := ioutil.ReadFile(rootFileConfig)
  200. if err != nil && !os.IsNotExist(err) {
  201. return err
  202. }
  203. if string(rootId) != "" {
  204. s.rootId = string(rootId)
  205. return nil
  206. }
  207. dir := &drive.File{
  208. Name: s.basedir,
  209. MimeType: GDriveDirectoryMimeType,
  210. }
  211. di, err := s.service.Files.Create(dir).Fields("id").Do()
  212. if err != nil {
  213. return err
  214. }
  215. s.rootId = di.Id
  216. err = ioutil.WriteFile(rootFileConfig, []byte(s.rootId), os.FileMode(0600))
  217. if err != nil {
  218. return err
  219. }
  220. return nil
  221. }
  222. func (s *GDrive) hasChecksum(f *drive.File) bool {
  223. return f.Md5Checksum != ""
  224. }
  225. func (s *GDrive) list(nextPageToken string, q string) (*drive.FileList, error) {
  226. return s.service.Files.List().Fields("nextPageToken, files(id, name, mimeType)").Q(q).PageToken(nextPageToken).Do()
  227. }
  228. func (s *GDrive) findId(filename string, token string) (string, error) {
  229. filename = strings.Replace(filename, `'`, `\'`, -1)
  230. filename = strings.Replace(filename, `"`, `\"`, -1)
  231. fileId, tokenId, nextPageToken := "", "", ""
  232. q := fmt.Sprintf("'%s' in parents and name='%s' and mimeType='%s' and trashed=false", s.rootId, token, GDriveDirectoryMimeType)
  233. l, err := s.list(nextPageToken, q)
  234. if err != nil {
  235. return "", err
  236. }
  237. for 0 < len(l.Files) {
  238. for _, fi := range l.Files {
  239. tokenId = fi.Id
  240. break
  241. }
  242. if l.NextPageToken == "" {
  243. break
  244. }
  245. l, err = s.list(l.NextPageToken, q)
  246. }
  247. if filename == "" {
  248. return tokenId, nil
  249. } else if tokenId == "" {
  250. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  251. }
  252. q = fmt.Sprintf("'%s' in parents and name='%s' and mimeType!='%s' and trashed=false", tokenId, filename, GDriveDirectoryMimeType)
  253. l, err = s.list(nextPageToken, q)
  254. if err != nil {
  255. return "", err
  256. }
  257. for 0 < len(l.Files) {
  258. for _, fi := range l.Files {
  259. fileId = fi.Id
  260. break
  261. }
  262. if l.NextPageToken == "" {
  263. break
  264. }
  265. l, err = s.list(l.NextPageToken, q)
  266. }
  267. if fileId == "" {
  268. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  269. }
  270. return fileId, nil
  271. }
  272. // Retrieve a token, saves the token, then returns the generated client.
  273. func getGDriveClient(config *oauth2.Config, localConfigPath string, logger *log.Logger) *http.Client {
  274. tokenFile := filepath.Join(localConfigPath, GDriveTokenJsonFile)
  275. tok, err := gDriveTokenFromFile(tokenFile)
  276. if err != nil {
  277. tok = getGDriveTokenFromWeb(config, logger)
  278. saveGDriveToken(tokenFile, tok, logger)
  279. }
  280. return config.Client(context.Background(), tok)
  281. }
  282. // Request a token from the web, then returns the retrieved token.
  283. func getGDriveTokenFromWeb(config *oauth2.Config, logger *log.Logger) *oauth2.Token {
  284. authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
  285. fmt.Printf("Go to the following link in your browser then type the "+
  286. "authorization code: \n%v\n", authURL)
  287. var authCode string
  288. if _, err := fmt.Scan(&authCode); err != nil {
  289. logger.Fatalf("Unable to read authorization code %v", err)
  290. }
  291. tok, err := config.Exchange(context.TODO(), authCode)
  292. if err != nil {
  293. logger.Fatalf("Unable to retrieve token from web %v", err)
  294. }
  295. return tok
  296. }
  297. // Retrieves a token from a local file.
  298. func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
  299. f, err := os.Open(file)
  300. defer f.Close()
  301. if err != nil {
  302. return nil, err
  303. }
  304. tok := &oauth2.Token{}
  305. err = json.NewDecoder(f).Decode(tok)
  306. return tok, err
  307. }
  308. // Saves a token to a file path.
  309. func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
  310. logger.Printf("Saving credential file to: %s\n", path)
  311. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  312. defer f.Close()
  313. if err != nil {
  314. logger.Fatalf("Unable to cache oauth token: %v", err)
  315. }
  316. json.NewEncoder(f).Encode(token)
  317. }