您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

388 行
9.0 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: fi.MimeType,
  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: fi.MimeType,
  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. //TODO: implement
  136. log.Printf("updating meta %s/%s with %v", token, filename, metadata)
  137. return nil
  138. }
  139. func (s *GDrive) Put(token string, filename string, reader io.Reader, metadata Metadata) error {
  140. dirId, err := s.findId("", token)
  141. if err != nil {
  142. return err
  143. }
  144. if dirId == "" {
  145. dir := &drive.File{
  146. Name: token,
  147. Parents: []string{s.rootId},
  148. MimeType: GDriveDirectoryMimeType,
  149. }
  150. di, err := s.service.Files.Create(dir).Fields("id").Do()
  151. if err != nil {
  152. return err
  153. }
  154. dirId = di.Id
  155. }
  156. // Instantiate empty drive file
  157. dst := &drive.File{
  158. Name: filename,
  159. Parents: []string{dirId},
  160. MimeType: metadata.ContentType,
  161. Properties: map[string]string{
  162. "downloads": strconv.Itoa(metadata.Downloads),
  163. "maxDownloads": strconv.Itoa(metadata.MaxDownloads),
  164. "deletionToken": metadata.DeletionToken,
  165. "deletionSecret": metadata.Secret,
  166. "expires": metadata.MaxDate.String(),
  167. },
  168. }
  169. ctx := context.Background()
  170. _, err = s.service.Files.Create(dst).Context(ctx).Media(reader, googleapi.ChunkSize(s.chunkSize)).Do()
  171. if err != nil {
  172. return err
  173. }
  174. return nil
  175. }
  176. func (s *GDrive) Delete(token string, filename string) (err error) {
  177. metadata, _ := s.findId(fmt.Sprintf("%s.metadata", filename), token)
  178. _ = s.service.Files.Delete(metadata).Do()
  179. var fileId string
  180. fileId, err = s.findId(filename, token)
  181. if err != nil {
  182. return
  183. }
  184. err = s.service.Files.Delete(fileId).Do()
  185. return
  186. }
  187. func (s *GDrive) IsNotExist(err error) bool {
  188. if err != nil {
  189. if e, ok := err.(*googleapi.Error); ok {
  190. return e.Code == http.StatusNotFound
  191. }
  192. }
  193. return false
  194. }
  195. func (s *GDrive) deleteExpired() error {
  196. //ToDo: figure out if necessary
  197. return nil
  198. }
  199. func (s *GDrive) setupRoot() error {
  200. rootFileConfig := filepath.Join(s.localConfigPath, GDriveRootConfigFile)
  201. rootId, err := ioutil.ReadFile(rootFileConfig)
  202. if err != nil && !os.IsNotExist(err) {
  203. return err
  204. }
  205. if string(rootId) != "" {
  206. s.rootId = string(rootId)
  207. return nil
  208. }
  209. dir := &drive.File{
  210. Name: s.basedir,
  211. MimeType: GDriveDirectoryMimeType,
  212. }
  213. di, err := s.service.Files.Create(dir).Fields("id").Do()
  214. if err != nil {
  215. return err
  216. }
  217. s.rootId = di.Id
  218. err = ioutil.WriteFile(rootFileConfig, []byte(s.rootId), os.FileMode(0600))
  219. if err != nil {
  220. return err
  221. }
  222. return nil
  223. }
  224. func (s *GDrive) hasChecksum(f *drive.File) bool {
  225. return f.Md5Checksum != ""
  226. }
  227. func (s *GDrive) list(nextPageToken string, q string) (*drive.FileList, error) {
  228. return s.service.Files.List().Fields("nextPageToken, files(id, name, mimeType)").Q(q).PageToken(nextPageToken).Do()
  229. }
  230. func (s *GDrive) findId(filename string, token string) (string, error) {
  231. filename = strings.Replace(filename, `'`, `\'`, -1)
  232. filename = strings.Replace(filename, `"`, `\"`, -1)
  233. fileId, tokenId, nextPageToken := "", "", ""
  234. q := fmt.Sprintf("'%s' in parents and name='%s' and mimeType='%s' and trashed=false", s.rootId, token, GDriveDirectoryMimeType)
  235. l, err := s.list(nextPageToken, q)
  236. if err != nil {
  237. return "", err
  238. }
  239. for 0 < len(l.Files) {
  240. for _, fi := range l.Files {
  241. tokenId = fi.Id
  242. break
  243. }
  244. if l.NextPageToken == "" {
  245. break
  246. }
  247. l, err = s.list(l.NextPageToken, q)
  248. }
  249. if filename == "" {
  250. return tokenId, nil
  251. } else if tokenId == "" {
  252. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  253. }
  254. q = fmt.Sprintf("'%s' in parents and name='%s' and mimeType!='%s' and trashed=false", tokenId, filename, GDriveDirectoryMimeType)
  255. l, err = s.list(nextPageToken, q)
  256. if err != nil {
  257. return "", err
  258. }
  259. for 0 < len(l.Files) {
  260. for _, fi := range l.Files {
  261. fileId = fi.Id
  262. break
  263. }
  264. if l.NextPageToken == "" {
  265. break
  266. }
  267. l, err = s.list(l.NextPageToken, q)
  268. }
  269. if fileId == "" {
  270. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  271. }
  272. return fileId, nil
  273. }
  274. // Retrieve a token, saves the token, then returns the generated client.
  275. func getGDriveClient(config *oauth2.Config, localConfigPath string, logger *log.Logger) *http.Client {
  276. tokenFile := filepath.Join(localConfigPath, GDriveTokenJsonFile)
  277. tok, err := gDriveTokenFromFile(tokenFile)
  278. if err != nil {
  279. tok = getGDriveTokenFromWeb(config, logger)
  280. saveGDriveToken(tokenFile, tok, logger)
  281. }
  282. return config.Client(context.Background(), tok)
  283. }
  284. // Request a token from the web, then returns the retrieved token.
  285. func getGDriveTokenFromWeb(config *oauth2.Config, logger *log.Logger) *oauth2.Token {
  286. authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
  287. fmt.Printf("Go to the following link in your browser then type the "+
  288. "authorization code: \n%v\n", authURL)
  289. var authCode string
  290. if _, err := fmt.Scan(&authCode); err != nil {
  291. logger.Fatalf("Unable to read authorization code %v", err)
  292. }
  293. tok, err := config.Exchange(context.TODO(), authCode)
  294. if err != nil {
  295. logger.Fatalf("Unable to retrieve token from web %v", err)
  296. }
  297. return tok
  298. }
  299. // Retrieves a token from a local file.
  300. func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
  301. f, err := os.Open(file)
  302. if err != nil {
  303. return nil, err
  304. }
  305. defer f.Close()
  306. tok := &oauth2.Token{}
  307. err = json.NewDecoder(f).Decode(tok)
  308. return tok, err
  309. }
  310. // Saves a token to a file path.
  311. func saveGDriveToken(path string, token *oauth2.Token, logger *log.Logger) {
  312. logger.Printf("Saving credential file to: %s\n", path)
  313. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  314. if err != nil {
  315. logger.Fatalf("Unable to cache oauth token: %v", err)
  316. }
  317. defer f.Close()
  318. _ = json.NewEncoder(f).Encode(token)
  319. }