Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 

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