service.go 7.6 KB

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