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.
 
 
 

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