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.
 
 
 

588 lines
13 KiB

  1. package server
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "log"
  7. "mime"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "sync"
  12. "github.com/goamz/goamz/s3"
  13. "encoding/json"
  14. "golang.org/x/oauth2"
  15. "golang.org/x/net/context"
  16. "golang.org/x/oauth2/google"
  17. "google.golang.org/api/drive/v3"
  18. "google.golang.org/api/googleapi"
  19. "net/http"
  20. "io/ioutil"
  21. )
  22. type Storage interface {
  23. Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error)
  24. Head(token string, filename string) (contentType string, contentLength uint64, err error)
  25. Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error
  26. IsNotExist(err error) bool
  27. Type() string
  28. }
  29. type LocalStorage struct {
  30. Storage
  31. basedir string
  32. }
  33. func NewLocalStorage(basedir string) (*LocalStorage, error) {
  34. return &LocalStorage{basedir: basedir}, nil
  35. }
  36. func (s *LocalStorage) Type() string {
  37. return "local"
  38. }
  39. func (s *LocalStorage) Head(token string, filename string) (contentType string, contentLength uint64, err error) {
  40. path := filepath.Join(s.basedir, token, filename)
  41. var fi os.FileInfo
  42. if fi, err = os.Lstat(path); err != nil {
  43. return
  44. }
  45. contentLength = uint64(fi.Size())
  46. contentType = mime.TypeByExtension(filepath.Ext(filename))
  47. return
  48. }
  49. func (s *LocalStorage) Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) {
  50. path := filepath.Join(s.basedir, token, filename)
  51. // content type , content length
  52. if reader, err = os.Open(path); err != nil {
  53. return
  54. }
  55. var fi os.FileInfo
  56. if fi, err = os.Lstat(path); err != nil {
  57. return
  58. }
  59. contentLength = uint64(fi.Size())
  60. contentType = mime.TypeByExtension(filepath.Ext(filename))
  61. return
  62. }
  63. func (s *LocalStorage) IsNotExist(err error) bool {
  64. if err == nil {
  65. return false
  66. }
  67. return os.IsNotExist(err)
  68. }
  69. func (s *LocalStorage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error {
  70. var f io.WriteCloser
  71. var err error
  72. path := filepath.Join(s.basedir, token)
  73. if err = os.Mkdir(path, 0700); err != nil && !os.IsExist(err) {
  74. return err
  75. }
  76. if f, err = os.OpenFile(filepath.Join(path, filename), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
  77. fmt.Printf("%s", err)
  78. return err
  79. }
  80. defer f.Close()
  81. if _, err = io.Copy(f, reader); err != nil {
  82. return err
  83. }
  84. return nil
  85. }
  86. type S3Storage struct {
  87. Storage
  88. bucket *s3.Bucket
  89. }
  90. func NewS3Storage(accessKey, secretKey, bucketName, endpoint string) (*S3Storage, error) {
  91. bucket, err := getBucket(accessKey, secretKey, bucketName, endpoint)
  92. if err != nil {
  93. return nil, err
  94. }
  95. return &S3Storage{bucket: bucket}, nil
  96. }
  97. func (s *S3Storage) Type() string {
  98. return "s3"
  99. }
  100. func (s *S3Storage) Head(token string, filename string) (contentType string, contentLength uint64, err error) {
  101. key := fmt.Sprintf("%s/%s", token, filename)
  102. // content type , content length
  103. response, err := s.bucket.Head(key, map[string][]string{})
  104. if err != nil {
  105. return
  106. }
  107. contentType = response.Header.Get("Content-Type")
  108. contentLength, err = strconv.ParseUint(response.Header.Get("Content-Length"), 10, 0)
  109. if err != nil {
  110. return
  111. }
  112. return
  113. }
  114. func (s *S3Storage) IsNotExist(err error) bool {
  115. if err == nil {
  116. return false
  117. }
  118. log.Printf("IsNotExist: %s, %#v", err.Error(), err)
  119. b := (err.Error() == "The specified key does not exist.")
  120. b = b || (err.Error() == "Access Denied")
  121. return b
  122. }
  123. func (s *S3Storage) Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) {
  124. key := fmt.Sprintf("%s/%s", token, filename)
  125. // content type , content length
  126. response, err := s.bucket.GetResponse(key)
  127. if err != nil {
  128. return
  129. }
  130. contentType = response.Header.Get("Content-Type")
  131. contentLength, err = strconv.ParseUint(response.Header.Get("Content-Length"), 10, 0)
  132. if err != nil {
  133. return
  134. }
  135. reader = response.Body
  136. return
  137. }
  138. func (s *S3Storage) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) (err error) {
  139. key := fmt.Sprintf("%s/%s", token, filename)
  140. var (
  141. multi *s3.Multi
  142. parts []s3.Part
  143. )
  144. if multi, err = s.bucket.InitMulti(key, contentType, s3.Private); err != nil {
  145. log.Printf(err.Error())
  146. return
  147. }
  148. // 20 mb parts
  149. partsChan := make(chan interface{})
  150. // partsChan := make(chan s3.Part)
  151. go func() {
  152. // maximize to 20 threads
  153. sem := make(chan int, 20)
  154. index := 1
  155. var wg sync.WaitGroup
  156. for {
  157. // buffered in memory because goamz s3 multi needs seekable reader
  158. var (
  159. buffer []byte = make([]byte, (1<<20)*10)
  160. count int
  161. err error
  162. )
  163. // Amazon expects parts of at least 5MB, except for the last one
  164. if count, err = io.ReadAtLeast(reader, buffer, (1<<20)*5); err != nil && err != io.ErrUnexpectedEOF && err != io.EOF {
  165. log.Printf(err.Error())
  166. return
  167. }
  168. // always send minimal 1 part
  169. if err == io.EOF && index > 1 {
  170. log.Printf("Waiting for all parts to finish uploading.")
  171. // wait for all parts to be finished uploading
  172. wg.Wait()
  173. // and close the channel
  174. close(partsChan)
  175. return
  176. }
  177. wg.Add(1)
  178. sem <- 1
  179. // using goroutines because of retries when upload fails
  180. go func(multi *s3.Multi, buffer []byte, index int) {
  181. log.Printf("Uploading part %d %d", index, len(buffer))
  182. defer func() {
  183. log.Printf("Finished part %d %d", index, len(buffer))
  184. wg.Done()
  185. <-sem
  186. }()
  187. partReader := bytes.NewReader(buffer)
  188. var part s3.Part
  189. if part, err = multi.PutPart(index, partReader); err != nil {
  190. log.Printf("Error while uploading part %d %d %s", index, len(buffer), err.Error())
  191. partsChan <- err
  192. return
  193. }
  194. log.Printf("Finished uploading part %d %d", index, len(buffer))
  195. partsChan <- part
  196. }(multi, buffer[:count], index)
  197. index++
  198. }
  199. }()
  200. // wait for all parts to be uploaded
  201. for part := range partsChan {
  202. switch part.(type) {
  203. case s3.Part:
  204. parts = append(parts, part.(s3.Part))
  205. case error:
  206. // abort multi upload
  207. log.Printf("Error during upload, aborting %s.", part.(error).Error())
  208. err = part.(error)
  209. multi.Abort()
  210. return
  211. }
  212. }
  213. log.Printf("Completing upload %d parts", len(parts))
  214. if err = multi.Complete(parts); err != nil {
  215. log.Printf("Error during completing upload %d parts %s", len(parts), err.Error())
  216. return
  217. }
  218. log.Printf("Completed uploading %d", len(parts))
  219. return
  220. }
  221. type GDrive struct {
  222. service *drive.Service
  223. rootId string
  224. basedir string
  225. localConfigPath string
  226. }
  227. func NewGDriveStorage(clientJsonFilepath string, localConfigPath string, basedir string) (*GDrive, error) {
  228. b, err := ioutil.ReadFile(clientJsonFilepath)
  229. if err != nil {
  230. return nil, err
  231. }
  232. // If modifying these scopes, delete your previously saved client_secret.json.
  233. config, err := google.ConfigFromJSON(b, drive.DriveScope, drive.DriveMetadataScope)
  234. if err != nil {
  235. return nil, err
  236. }
  237. srv, err := drive.New(getGDriveClient(config))
  238. if err != nil {
  239. return nil, err
  240. }
  241. storage := &GDrive{service: srv, basedir: basedir, rootId: "", localConfigPath:localConfigPath}
  242. err = storage.setupRoot()
  243. if err != nil {
  244. return nil, err
  245. }
  246. return storage, nil
  247. }
  248. const GDriveRootConfigFile = "root_id.conf"
  249. const GDriveDirectoryMimeType = "application/vnd.google-apps.folder"
  250. func (s *GDrive) setupRoot() error {
  251. rootFileConfig := filepath.Join(s.localConfigPath, GDriveRootConfigFile)
  252. rootId, err := ioutil.ReadFile(rootFileConfig)
  253. if err != nil && !os.IsNotExist(err) {
  254. return err
  255. }
  256. if string(rootId) != "" {
  257. s.rootId = string(rootId)
  258. return nil
  259. }
  260. dir := &drive.File{
  261. Name: s.basedir,
  262. MimeType: GDriveDirectoryMimeType,
  263. }
  264. di, err := s.service.Files.Create(dir).Fields("id").Do()
  265. if err != nil {
  266. return err
  267. }
  268. s.rootId = di.Id
  269. err = ioutil.WriteFile(rootFileConfig, []byte(s.rootId), os.FileMode(0600))
  270. if err != nil {
  271. return err
  272. }
  273. return nil
  274. }
  275. func (s *GDrive) hasChecksum(f *drive.File) bool {
  276. return f.Md5Checksum != ""
  277. }
  278. func (s *GDrive) list(nextPageToken string, q string) (*drive.FileList, error){
  279. return s.service.Files.List().Fields("nextPageToken, files(id, name, mimeType)").Q(q).PageToken(nextPageToken).Do()
  280. }
  281. func (s *GDrive) findId(filename string, token string) (string, error) {
  282. fileId, tokenId, nextPageToken := "", "", ""
  283. q := fmt.Sprintf("'%s' in parents and name='%s' and mimeType='%s' and trashed=false", s.rootId, token, GDriveDirectoryMimeType)
  284. l, err := s.list(nextPageToken, q)
  285. for 0 < len(l.Files) {
  286. if err != nil {
  287. return "", err
  288. }
  289. for _, fi := range l.Files {
  290. tokenId = fi.Id
  291. break
  292. }
  293. if l.NextPageToken == "" {
  294. break
  295. }
  296. l, err = s.list(l.NextPageToken, q)
  297. }
  298. if filename == "" {
  299. return tokenId, nil
  300. } else if tokenId == "" {
  301. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  302. }
  303. q = fmt.Sprintf("'%s' in parents and name='%s' and mimeType!='%s' and trashed=false", tokenId, filename, GDriveDirectoryMimeType)
  304. l, err = s.list(nextPageToken, q)
  305. for 0 < len(l.Files) {
  306. if err != nil {
  307. return "", err
  308. }
  309. for _, fi := range l.Files {
  310. fileId = fi.Id
  311. break
  312. }
  313. if l.NextPageToken == "" {
  314. break
  315. }
  316. l, err = s.list(l.NextPageToken, q)
  317. }
  318. if fileId == "" {
  319. return "", fmt.Errorf("Cannot find file %s/%s", token, filename)
  320. }
  321. return fileId, nil
  322. }
  323. func (s *GDrive) Type() string {
  324. return "gdrive"
  325. }
  326. func (s *GDrive) Head(token string, filename string) (contentType string, contentLength uint64, err error) {
  327. var fileId string
  328. fileId, err = s.findId(filename, token)
  329. if err != nil {
  330. return
  331. }
  332. var fi *drive.File
  333. if fi, err = s.service.Files.Get(fileId).Fields("mimeType", "size").Do(); err != nil {
  334. return
  335. }
  336. contentLength = uint64(fi.Size)
  337. contentType = fi.MimeType
  338. return
  339. }
  340. func (s *GDrive) Get(token string, filename string) (reader io.ReadCloser, contentType string, contentLength uint64, err error) {
  341. var fileId string
  342. fileId, err = s.findId(filename, token)
  343. if err != nil {
  344. return
  345. }
  346. var fi *drive.File
  347. fi, err = s.service.Files.Get(fileId).Fields("mimeType", "size", "md5Checksum").Do()
  348. if !s.hasChecksum(fi) {
  349. err = fmt.Errorf("Cannot find file %s/%s", token, filename)
  350. return
  351. }
  352. contentLength = uint64(fi.Size)
  353. contentType = fi.MimeType
  354. ctx := context.Background()
  355. var res *http.Response
  356. res, err = s.service.Files.Get(fileId).Context(ctx).Download()
  357. if err != nil {
  358. return
  359. }
  360. reader = res.Body
  361. return
  362. }
  363. func (s *GDrive) IsNotExist(err error) bool {
  364. if err == nil {
  365. return false
  366. }
  367. if err != nil {
  368. if e, ok := err.(*googleapi.Error); ok {
  369. return e.Code == http.StatusNotFound
  370. }
  371. }
  372. return false
  373. }
  374. func (s *GDrive) Put(token string, filename string, reader io.Reader, contentType string, contentLength uint64) error {
  375. dirId, err := s.findId("", token)
  376. if err != nil {
  377. return err
  378. }
  379. if dirId == "" {
  380. dir := &drive.File{
  381. Name: token,
  382. Parents: []string{s.rootId},
  383. MimeType: GDriveDirectoryMimeType,
  384. }
  385. di, err := s.service.Files.Create(dir).Fields("id").Do()
  386. if err != nil {
  387. return err
  388. }
  389. dirId = di.Id
  390. }
  391. // Instantiate empty drive file
  392. dst := &drive.File{
  393. Name: filename,
  394. Parents: []string{dirId},
  395. MimeType: contentType,
  396. }
  397. ctx := context.Background()
  398. _, err = s.service.Files.Create(dst).Context(ctx).Media(reader).Do()
  399. if err != nil {
  400. return err
  401. }
  402. return nil
  403. }
  404. // Retrieve a token, saves the token, then returns the generated client.
  405. func getGDriveClient(config *oauth2.Config) *http.Client {
  406. tokenFile := "token.json"
  407. tok, err := gDriveTokenFromFile(tokenFile)
  408. if err != nil {
  409. tok = getGDriveTokenFromWeb(config)
  410. saveGDriveToken(tokenFile, tok)
  411. }
  412. return config.Client(context.Background(), tok)
  413. }
  414. // Request a token from the web, then returns the retrieved token.
  415. func getGDriveTokenFromWeb(config *oauth2.Config) *oauth2.Token {
  416. authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
  417. fmt.Printf("Go to the following link in your browser then type the "+
  418. "authorization code: \n%v\n", authURL)
  419. var authCode string
  420. if _, err := fmt.Scan(&authCode); err != nil {
  421. log.Fatalf("Unable to read authorization code %v", err)
  422. }
  423. tok, err := config.Exchange(oauth2.NoContext, authCode)
  424. if err != nil {
  425. log.Fatalf("Unable to retrieve token from web %v", err)
  426. }
  427. return tok
  428. }
  429. // Retrieves a token from a local file.
  430. func gDriveTokenFromFile(file string) (*oauth2.Token, error) {
  431. f, err := os.Open(file)
  432. defer f.Close()
  433. if err != nil {
  434. return nil, err
  435. }
  436. tok := &oauth2.Token{}
  437. err = json.NewDecoder(f).Decode(tok)
  438. return tok, err
  439. }
  440. // Saves a token to a file path.
  441. func saveGDriveToken(path string, token *oauth2.Token) {
  442. fmt.Printf("Saving credential file to: %s\n", path)
  443. f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
  444. defer f.Close()
  445. if err != nil {
  446. log.Fatalf("Unable to cache oauth token: %v", err)
  447. }
  448. json.NewEncoder(f).Encode(token)
  449. }