service.go 7.6 KB

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