gdrivefs.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. // gdrivemount implements a google drive fuse driver
  2. package gdrivefs
  3. import (
  4. "io"
  5. "os"
  6. "syscall"
  7. "time"
  8. "dev.hexasoftware.com/hxs/cloudmount/cloudfs"
  9. "dev.hexasoftware.com/hxs/prettylog"
  10. "golang.org/x/net/context"
  11. drive "google.golang.org/api/drive/v3"
  12. "google.golang.org/api/googleapi"
  13. "github.com/jacobsa/fuse"
  14. "github.com/jacobsa/fuse/fuseops"
  15. "github.com/jacobsa/fuse/fuseutil"
  16. )
  17. var (
  18. log = prettylog.New("gdrivemount")
  19. )
  20. type fileHandle struct {
  21. handleID fuseops.HandleID
  22. entry *FileEntry
  23. uploadOnDone bool
  24. // Handling for dir
  25. entries []fuseutil.Dirent
  26. }
  27. /*type DirEntry struct {
  28. file *FileEntry
  29. }*/
  30. // FuseHndler handler
  31. type GDriveFS struct {
  32. fuseutil.NotImplementedFileSystem // Defaults
  33. core *cloudfs.Core // Core Config instead?
  34. client *drive.Service
  35. root *FileEntry // hiearchy reference
  36. fileHandles map[fuseops.HandleID]*fileHandle
  37. nextRefresh time.Time
  38. //fileMap map[string]
  39. // Map IDS with FileEntries
  40. }
  41. func New(core *cloudfs.Core) cloudfs.Driver {
  42. fs := &GDriveFS{
  43. core: core,
  44. fileHandles: map[fuseops.HandleID]*fileHandle{},
  45. }
  46. fs.initClient() // Init Oauth2 client
  47. rootEntry := fs.NewFileEntry()
  48. rootEntry.Attr = fuseops.InodeAttributes{
  49. Mode: os.FileMode(0755) | os.ModeDir,
  50. Uid: core.Config.UID,
  51. Gid: core.Config.GID,
  52. }
  53. rootEntry.Inode = fuseops.RootInodeID
  54. rootEntry.isDir = true
  55. fs.root = rootEntry
  56. // Temporary entry
  57. entry := fs.root.AppendGFile(&drive.File{Id: "0", Name: "Loading..."}, 999999)
  58. entry.Attr.Mode = os.FileMode(0)
  59. return fs
  60. }
  61. func (fs *GDriveFS) Start() {
  62. fs.timedRefresh()
  63. }
  64. func (fs *GDriveFS) NewFileEntry() *FileEntry {
  65. return &FileEntry{
  66. fs: fs,
  67. children: []*FileEntry{},
  68. Attr: fuseops.InodeAttributes{},
  69. }
  70. }
  71. ////////////////////////////////////////////////////////
  72. // TOOLS & HELPERS
  73. ////////////////////////////////////////////////////////
  74. func (fs *GDriveFS) createHandle() *fileHandle {
  75. // Lock here instead
  76. var handle fuseops.HandleID
  77. for handle = 1; handle < 99999; handle++ {
  78. _, ok := fs.fileHandles[handle]
  79. if !ok {
  80. break
  81. }
  82. }
  83. fh := &fileHandle{handleID: handle}
  84. fs.fileHandles[handle] = fh
  85. return fh
  86. }
  87. // Cache somewhere?
  88. /*func (fs *GDriveFS) getUID() uint32 {
  89. uid, _ := strconv.Atoi(fs.osuser.Uid)
  90. return uint32(uid)
  91. }
  92. func (fs *GDriveFS) getGID() uint32 {
  93. gid, _ := strconv.Atoi(fs.osuser.Gid)
  94. return uint32(gid)
  95. }*/
  96. func (fs *GDriveFS) timedRefresh() {
  97. go func() {
  98. for {
  99. if time.Now().After(fs.nextRefresh) {
  100. fs.Refresh()
  101. }
  102. time.Sleep(2 * time.Minute) // 2 minutes
  103. }
  104. }()
  105. }
  106. // Refresh service files
  107. func (fs *GDriveFS) Refresh() {
  108. fs.nextRefresh = time.Now().Add(1 * time.Minute)
  109. fileList := []*drive.File{}
  110. fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
  111. gdFields := googleapi.Field("nextPageToken, files(id,name,size,quotaBytesUsed, mimeType,parents,createdTime,modifiedTime)")
  112. log.Println("Loading file entries from gdrive")
  113. r, err := fs.client.Files.List().
  114. OrderBy("createdTime").
  115. PageSize(1000).
  116. SupportsTeamDrives(true).
  117. IncludeTeamDriveItems(true).
  118. Fields(gdFields).
  119. Do()
  120. if err != nil {
  121. log.Println("GDrive ERR:", err)
  122. return
  123. }
  124. fileList = append(fileList, r.Files...)
  125. // Rest of the pages
  126. for r.NextPageToken != "" {
  127. r, err = fs.client.Files.List().
  128. OrderBy("createdTime").
  129. PageToken(r.NextPageToken).
  130. Fields(gdFields).
  131. Do()
  132. if err != nil {
  133. log.Println("GDrive ERR:", err)
  134. return
  135. }
  136. fileList = append(fileList, r.Files...)
  137. }
  138. log.Println("Total entries:", len(fileList))
  139. // TimeSort
  140. /*log.Println("Sort by time")
  141. sort.Slice(fileList, func(i, j int) bool {
  142. createdTimeI, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
  143. createdTimeJ, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
  144. if createdTimeI.Before(createdTimeJ) {
  145. return true
  146. }
  147. return false
  148. })*/
  149. // Cache ID for faster retrieval, might not be necessary
  150. for _, f := range fileList {
  151. fileMap[f.Id] = f
  152. }
  153. if err != nil || r == nil {
  154. log.Println("Unable to retrieve files", err)
  155. return
  156. }
  157. // Create clean fileList
  158. root := fs.NewFileEntry()
  159. // Helper func to recurse
  160. // Everything loaded we add to our entries
  161. // Add file and its parents priorizing it parent
  162. var appendFile func(df *drive.File)
  163. appendFile = func(df *drive.File) {
  164. for _, pID := range df.Parents {
  165. parentFile, ok := fileMap[pID]
  166. if !ok {
  167. parentFile, err = fs.client.Files.Get(pID).Do()
  168. if err != nil {
  169. panic(err)
  170. }
  171. fileMap[parentFile.Id] = parentFile
  172. }
  173. appendFile(parentFile) // Recurse
  174. }
  175. // Find existing entry
  176. var inode fuseops.InodeID
  177. entry := fs.root.FindByGID(df.Id, true)
  178. if entry == nil {
  179. inode = root.FindUnusedInode()
  180. } else {
  181. inode = entry.Inode
  182. }
  183. newEntry := root.solveAppendGFile(df, inode) // Find right parent
  184. if entry != nil && entry.GFile.Name == df.Name { // Copy name from old entry
  185. newEntry.Name = entry.Name
  186. }
  187. // add File
  188. }
  189. for _, f := range fileList { // Ordered
  190. appendFile(f) // Check parent first
  191. }
  192. log.Println("Refresh done, update root")
  193. fs.root.children = root.children
  194. log.Println("File count:", fs.root.Count())
  195. }
  196. // OpenDir return nil error allows open dir
  197. func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
  198. entry := fs.root.FindByInode(op.Inode, true)
  199. if entry == nil {
  200. return fuse.ENOENT
  201. }
  202. fh := fs.createHandle()
  203. fh.entry = entry
  204. op.Handle = fh.handleID
  205. return // No error allow, dir open
  206. }
  207. // ReadDir lists files into readdirop
  208. func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
  209. fh, ok := fs.fileHandles[op.Handle]
  210. if !ok {
  211. log.Fatal("Handle does not exists")
  212. }
  213. if op.Offset == 0 { // Rebuild/rewind dir list
  214. fh.entries = []fuseutil.Dirent{}
  215. for i, v := range fh.entry.children {
  216. fusetype := fuseutil.DT_File
  217. if v.isDir {
  218. fusetype = fuseutil.DT_Directory
  219. }
  220. dirEnt := fuseutil.Dirent{
  221. Inode: v.Inode,
  222. Name: v.Name,
  223. Type: fusetype,
  224. Offset: fuseops.DirOffset(i) + 1,
  225. }
  226. // written += fuseutil.WriteDirent(fh.buf[written:], dirEnt)
  227. fh.entries = append(fh.entries, dirEnt)
  228. }
  229. }
  230. index := int(op.Offset)
  231. if index > len(fh.entries) {
  232. return fuse.EINVAL
  233. }
  234. if index > 0 {
  235. index++
  236. }
  237. for i := index; i < len(fh.entries); i++ {
  238. n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
  239. //log.Println("Written:", n)
  240. if n == 0 {
  241. break
  242. }
  243. op.BytesRead += n
  244. }
  245. return
  246. }
  247. // SetInodeAttributes Not sure what attributes gdrive support we just leave this blank for now
  248. func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
  249. // Hack to truncate file?
  250. if op.Size != nil {
  251. f := fs.root.FindByInode(op.Inode, true)
  252. if *op.Size != 0 { // We only allow truncate to 0
  253. return fuse.ENOSYS
  254. }
  255. // Delete and create another on truncate 0
  256. err = fs.client.Files.Delete(f.GFile.Id).Do() // XXX: Careful on this
  257. createdFile, err := fs.client.Files.Create(&drive.File{Parents: f.GFile.Parents, Name: f.GFile.Name}).Do()
  258. if err != nil {
  259. return fuse.EINVAL
  260. }
  261. f.SetGFile(createdFile) // Set new file
  262. }
  263. return
  264. }
  265. //GetInodeAttributes return attributes
  266. func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
  267. f := fs.root.FindByInode(op.Inode, true)
  268. if f == nil {
  269. return fuse.ENOENT
  270. }
  271. op.Attributes = f.Attr
  272. op.AttributesExpiration = time.Now().Add(time.Minute)
  273. return
  274. }
  275. // ReleaseDirHandle deletes file handle entry
  276. func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
  277. delete(fs.fileHandles, op.Handle)
  278. return
  279. }
  280. // LookUpInode based on Parent and Name we return a self cached inode
  281. func (fs *GDriveFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
  282. parentFile := fs.root.FindByInode(op.Parent, true) // true means transverse all
  283. if parentFile == nil {
  284. return fuse.ENOENT
  285. }
  286. now := time.Now()
  287. // Transverse only local
  288. f := parentFile.FindByName(op.Name, false)
  289. if f == nil {
  290. return fuse.ENOENT
  291. }
  292. op.Entry = fuseops.ChildInodeEntry{
  293. Attributes: f.Attr,
  294. Child: f.Inode,
  295. AttributesExpiration: now.Add(time.Second),
  296. EntryExpiration: now.Add(time.Second),
  297. }
  298. return
  299. }
  300. // StatFS basically allows StatFS to run
  301. /*func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
  302. return
  303. }*/
  304. // ForgetInode allows to forgetInode
  305. func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
  306. return
  307. }
  308. // GetXAttr special attributes
  309. func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
  310. return
  311. }
  312. //////////////////////////////////////////////////////////////////////////
  313. // File OPS
  314. //////////////////////////////////////////////////////////////////////////
  315. // OpenFile creates a temporary handle to be handled on read or write
  316. func (fs *GDriveFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
  317. f := fs.root.FindByInode(op.Inode, true) // might not exists
  318. // Generate new handle
  319. fh := fs.createHandle()
  320. fh.entry = f
  321. op.Handle = fh.handleID
  322. op.UseDirectIO = true
  323. return
  324. }
  325. // ReadFile if the first time we download the google drive file into a local temporary file
  326. func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
  327. lf := fs.fileHandles[op.Handle]
  328. localFile := lf.entry.Cache()
  329. op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
  330. if err == io.EOF { // fuse does not expect a EOF
  331. err = nil
  332. }
  333. return
  334. }
  335. // CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
  336. func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
  337. parentFile := fs.root.FindByInode(op.Parent, true)
  338. if parentFile == nil {
  339. return fuse.ENOENT
  340. }
  341. // Only write on child folders
  342. if parentFile.Inode == fuseops.RootInodeID {
  343. return syscall.EPERM
  344. }
  345. existsFile := parentFile.FindByName(op.Name, false)
  346. if existsFile != nil {
  347. return fuse.EEXIST
  348. }
  349. // Generate ID
  350. //genId, err := fs.client.Files.GenerateIds().Count(1).Do()
  351. //id := genId.Ids[0]
  352. parents := []string{parentFile.GFile.Id}
  353. newFile := &drive.File{
  354. Parents: parents,
  355. Name: op.Name,
  356. }
  357. createdFile, err := fs.client.Files.Create(newFile).Do()
  358. if err != nil {
  359. err = fuse.EINVAL
  360. return
  361. }
  362. entry := parentFile.AppendGFile(createdFile, fs.root.FindUnusedInode()) // Add new created file
  363. if entry == nil {
  364. err = fuse.EINVAL
  365. return
  366. }
  367. localFile := entry.Cache()
  368. if localFile == nil {
  369. return fuse.EINVAL
  370. }
  371. // Associate a temp file to a new handle
  372. // Local copy
  373. // Lock
  374. fh := fs.createHandle()
  375. fh.entry = entry
  376. fh.uploadOnDone = true
  377. //
  378. op.Handle = fh.handleID
  379. op.Entry = fuseops.ChildInodeEntry{
  380. Attributes: entry.Attr,
  381. Child: entry.Inode,
  382. AttributesExpiration: time.Now().Add(time.Minute),
  383. EntryExpiration: time.Now().Add(time.Minute),
  384. }
  385. op.Mode = entry.Attr.Mode
  386. return
  387. }
  388. // WriteFile as ReadFile it creates a temporary file on first read
  389. // Maybe the ReadFile should be called here aswell to cache current contents since we are using writeAt
  390. func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
  391. lf, ok := fs.fileHandles[op.Handle]
  392. if !ok {
  393. return fuse.EIO
  394. }
  395. localFile := lf.entry.Cache()
  396. if localFile == nil {
  397. return fuse.EINVAL
  398. }
  399. _, err = localFile.WriteAt(op.Data, op.Offset)
  400. if err != nil {
  401. err = fuse.EIO
  402. return
  403. }
  404. lf.uploadOnDone = true
  405. return
  406. }
  407. // FlushFile just returns no error, maybe upload should be handled here
  408. func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
  409. lf, ok := fs.fileHandles[op.Handle]
  410. if !ok {
  411. return fuse.EIO
  412. }
  413. if lf.entry.tempFile == nil {
  414. return
  415. }
  416. if lf.uploadOnDone { // or if content changed basically
  417. err = lf.entry.Sync()
  418. if err != nil {
  419. return fuse.EINVAL
  420. }
  421. }
  422. return
  423. }
  424. // ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
  425. func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
  426. lf := fs.fileHandles[op.Handle]
  427. /*if lf.uploadOnDone {
  428. err = lf.entry.Sync()
  429. if err != nil {
  430. return fuse.EINVAL
  431. }
  432. }*/
  433. lf.entry.ClearCache()
  434. delete(fs.fileHandles, op.Handle)
  435. return
  436. }
  437. // Unlink remove file and remove from local cache entry
  438. func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
  439. parentEntry := fs.root.FindByInode(op.Parent, true)
  440. if parentEntry == nil {
  441. return fuse.ENOENT
  442. }
  443. if parentEntry.Inode == fuseops.RootInodeID {
  444. return syscall.EPERM
  445. }
  446. fileEntry := parentEntry.FindByName(op.Name, false)
  447. if fileEntry == nil {
  448. return fuse.ENOATTR
  449. }
  450. err = fs.client.Files.Delete(fileEntry.GFile.Id).Do()
  451. if err != nil {
  452. return fuse.EIO
  453. }
  454. parentEntry.RemoveChild(fileEntry)
  455. return
  456. }
  457. // MkDir creates a directory on a parent dir
  458. func (fs *GDriveFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
  459. parentFile := fs.root.FindByInode(op.Parent, true)
  460. if parentFile == nil {
  461. return fuse.ENOENT
  462. }
  463. if parentFile.Inode == fuseops.RootInodeID {
  464. return syscall.EPERM
  465. }
  466. // Should check existent first too
  467. fi, err := fs.client.Files.Create(&drive.File{
  468. Parents: []string{parentFile.GFile.Id},
  469. MimeType: "application/vnd.google-apps.folder",
  470. Name: op.Name,
  471. }).Do()
  472. if err != nil {
  473. return fuse.ENOATTR
  474. }
  475. entry := parentFile.AppendGFile(fi, fs.root.FindUnusedInode())
  476. if entry == nil {
  477. return fuse.EINVAL
  478. }
  479. op.Entry = fuseops.ChildInodeEntry{
  480. Attributes: entry.Attr,
  481. Child: entry.Inode,
  482. AttributesExpiration: time.Now().Add(time.Minute),
  483. EntryExpiration: time.Now().Add(time.Microsecond),
  484. }
  485. return
  486. }
  487. // RmDir fuse implementation
  488. func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
  489. parentFile := fs.root.FindByInode(op.Parent, true)
  490. if parentFile == nil {
  491. return fuse.ENOENT
  492. }
  493. if parentFile.Inode == fuseops.RootInodeID {
  494. return syscall.EPERM
  495. }
  496. theFile := parentFile.FindByName(op.Name, false)
  497. err = fs.client.Files.Delete(theFile.GFile.Id).Do()
  498. if err != nil {
  499. return fuse.ENOTEMPTY
  500. }
  501. parentFile.RemoveChild(theFile)
  502. // Remove from entry somehow
  503. return
  504. }
  505. // Rename fuse implementation
  506. func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
  507. oldParentFile := fs.root.FindByInode(op.OldParent, true)
  508. if oldParentFile == nil {
  509. return fuse.ENOENT
  510. }
  511. newParentFile := fs.root.FindByInode(op.NewParent, true)
  512. if newParentFile == nil {
  513. return fuse.ENOENT
  514. }
  515. if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
  516. return syscall.EPERM
  517. }
  518. oldFile := oldParentFile.FindByName(op.OldName, false)
  519. // Although GDrive allows duplicate names, there is some issue with inode caching
  520. // So we prevent a rename to a file with same name
  521. existsFile := newParentFile.FindByName(op.NewName, false)
  522. if existsFile != nil {
  523. return fuse.EEXIST
  524. }
  525. ngFile := &drive.File{
  526. Name: op.NewName,
  527. }
  528. updateCall := fs.client.Files.Update(oldFile.GFile.Id, ngFile)
  529. if oldParentFile != newParentFile {
  530. updateCall.RemoveParents(oldParentFile.GFile.Id)
  531. updateCall.AddParents(newParentFile.GFile.Id)
  532. }
  533. updatedFile, err := updateCall.Do()
  534. oldParentFile.RemoveChild(oldFile)
  535. newParentFile.AppendGFile(updatedFile, oldFile.Inode)
  536. return
  537. }