service.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. package gdrivefs
  2. import (
  3. "io"
  4. "net/http"
  5. "os"
  6. "time"
  7. "github.com/jacobsa/fuse"
  8. "dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
  9. drive "google.golang.org/api/drive/v3"
  10. "google.golang.org/api/googleapi"
  11. )
  12. const (
  13. fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
  14. gdFields = googleapi.Field("files(" + fileFields + ")")
  15. )
  16. type Service struct {
  17. client *drive.Service
  18. savedStartPageToken string
  19. }
  20. func (s *Service) Changes() ([]*drive.Change, error) { // Return a list of New file entries
  21. if s.savedStartPageToken == "" {
  22. startPageTokenRes, err := s.client.Changes.GetStartPageToken().Do()
  23. if err != nil {
  24. log.Println("GDrive err", err)
  25. }
  26. s.savedStartPageToken = startPageTokenRes.StartPageToken
  27. }
  28. ret := []*drive.Change{}
  29. pageToken := s.savedStartPageToken
  30. for pageToken != "" {
  31. changesRes, err := s.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
  32. if err != nil {
  33. log.Println("Err fetching changes", err)
  34. break
  35. }
  36. //log.Println("Changes:", len(changesRes.Changes))
  37. for _, c := range changesRes.Changes {
  38. ret = append(ret, c) // Convert to our changes
  39. }
  40. if changesRes.NewStartPageToken != "" {
  41. s.savedStartPageToken = changesRes.NewStartPageToken
  42. }
  43. pageToken = changesRes.NextPageToken
  44. }
  45. return ret, nil
  46. }
  47. func (s *Service) ListAll() ([]*basefs.File, error) {
  48. fileList := []*drive.File{}
  49. // Service list ALL ???
  50. fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
  51. r, err := s.client.Files.List().
  52. OrderBy("createdTime").
  53. PageSize(1000).
  54. SupportsTeamDrives(true).
  55. IncludeTeamDriveItems(true).
  56. Fields(googleapi.Field("nextPageToken"), gdFields).
  57. Do()
  58. if err != nil {
  59. // Sometimes gdrive returns error 500 randomly
  60. log.Println("GDrive ERR:", err)
  61. return s.ListAll() // retry
  62. //return nil, err
  63. }
  64. fileList = append(fileList, r.Files...)
  65. // Rest of the pages
  66. for r.NextPageToken != "" {
  67. r, err = s.client.Files.List().
  68. OrderBy("createdTime").
  69. PageToken(r.NextPageToken).
  70. Fields(googleapi.Field("nextPageToken"), gdFields).
  71. Do()
  72. if err != nil {
  73. log.Println("GDrive ERR:", err)
  74. return s.ListAll() // retry
  75. //return nil, err
  76. }
  77. fileList = append(fileList, r.Files...)
  78. }
  79. log.Println("Total entries:", len(fileList))
  80. // Cache ID for faster retrieval, might not be necessary
  81. for _, f := range fileList { // Temporary lookup Cache
  82. fileMap[f.Id] = f
  83. }
  84. // All fetched
  85. files := []*basefs.File{}
  86. // Create clean fileList
  87. var appendFile func(gfile *drive.File)
  88. appendFile = func(gfile *drive.File) {
  89. for _, pID := range gfile.Parents {
  90. parentFile, ok := fileMap[pID]
  91. if !ok {
  92. parentFile, err = s.client.Files.Get(pID).Do()
  93. if err != nil {
  94. log.Println("Error fetching single file:", err)
  95. }
  96. fileMap[parentFile.Id] = parentFile
  97. appendFile(parentFile) // Recurse
  98. }
  99. }
  100. // Do not append directly
  101. files = append(files, File(gfile)) // Add converted file
  102. }
  103. for _, f := range fileList { // Ordered
  104. appendFile(f) // Check parent first
  105. }
  106. log.Println("File count:", len(files))
  107. return files, nil
  108. }
  109. func (s *Service) Create(parent *basefs.File, name string, isDir bool) (*basefs.File, error) {
  110. newGFile := &drive.File{
  111. Parents: []string{parent.ID},
  112. Name: name,
  113. }
  114. if isDir {
  115. newGFile.MimeType = "application/vnd.google-apps.folder"
  116. }
  117. // Could be transformed to CreateFile in continer
  118. createdGFile, err := s.client.Files.Create(newGFile).Fields(fileFields).Do()
  119. if err != nil {
  120. return nil, fuse.EINVAL
  121. }
  122. return File(createdGFile), nil
  123. }
  124. func (s *Service) Upload(reader io.Reader, file *basefs.File) (*basefs.File, error) {
  125. ngFile := &drive.File{}
  126. up := s.client.Files.Update(file.ID, ngFile)
  127. upFile, err := up.Media(reader).Fields(fileFields).Do()
  128. if err != nil {
  129. return nil, err
  130. }
  131. return File(upFile), nil
  132. }
  133. func (s *Service) DownloadTo(w io.Writer, file *basefs.File) error {
  134. var res *http.Response
  135. var err error
  136. // TODO :Place this in service Download
  137. gfile := file.Data.(*drive.File)
  138. // Export GDocs (Special google doc documents needs to be exported make a config somewhere for this)
  139. switch gfile.MimeType { // Make this somewhat optional special case
  140. case "application/vnd.google-apps.document":
  141. res, err = s.client.Files.Export(gfile.Id, "text/plain").Download()
  142. case "application/vnd.google-apps.spreadsheet":
  143. res, err = s.client.Files.Export(gfile.Id, "text/csv").Download()
  144. default:
  145. res, err = s.client.Files.Get(gfile.Id).Download()
  146. }
  147. if err != nil {
  148. log.Println("Error from GDrive API", err)
  149. return err
  150. }
  151. defer res.Body.Close()
  152. io.Copy(w, res.Body)
  153. return nil
  154. }
  155. func (s *Service) Move(file *basefs.File, newParent *basefs.File, name string) (*basefs.File, error) {
  156. ngFile := &drive.File{
  157. Name: name,
  158. }
  159. updateCall := s.client.Files.Update(file.ID, ngFile).Fields(fileFields)
  160. if !file.HasParent(newParent) {
  161. for _, pgid := range file.Parents {
  162. updateCall.RemoveParents(pgid) // Remove all parents??
  163. }
  164. updateCall.AddParents(newParent.ID)
  165. }
  166. // }
  167. /*if oldParentFile != newParentFile {
  168. updateCall.RemoveParents(oldParentFile.GID)
  169. updateCall.AddParents(newParentFile.GID)
  170. }*/
  171. updatedFile, err := updateCall.Do()
  172. return File(updatedFile), err
  173. }
  174. func (s *Service) Delete(file *basefs.File) error {
  175. err := s.client.Files.Delete(file.ID).Do()
  176. if err != nil {
  177. return err
  178. }
  179. return nil
  180. }
  181. func File(gfile *drive.File) *basefs.File {
  182. if gfile == nil {
  183. return nil
  184. }
  185. createdTime, _ := time.Parse(time.RFC3339, gfile.CreatedTime)
  186. modifiedTime, _ := time.Parse(time.RFC3339, gfile.ModifiedTime)
  187. mode := os.FileMode(0644)
  188. if gfile.MimeType == "application/vnd.google-apps.folder" {
  189. mode = os.FileMode(0755) | os.ModeDir
  190. }
  191. file := &basefs.File{
  192. ID: gfile.Id,
  193. Name: gfile.Name,
  194. Size: uint64(gfile.Size),
  195. CreatedTime: createdTime,
  196. ModifiedTime: modifiedTime,
  197. AccessedTime: modifiedTime,
  198. Mode: mode,
  199. Parents: gfile.Parents,
  200. Data: gfile, // Extra gfile
  201. }
  202. return file
  203. }