|
@@ -0,0 +1,617 @@
|
|
|
|
+package gdrivemount
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "fmt"
|
|
|
|
+ "io"
|
|
|
|
+ "io/ioutil"
|
|
|
|
+ "net/http"
|
|
|
|
+ "os"
|
|
|
|
+ "os/user"
|
|
|
|
+ "strconv"
|
|
|
|
+ "time"
|
|
|
|
+
|
|
|
|
+ "dev.hexasoftware.com/hxs/prettylog"
|
|
|
|
+
|
|
|
|
+ "golang.org/x/net/context"
|
|
|
|
+
|
|
|
|
+ drive "google.golang.org/api/drive/v3"
|
|
|
|
+ "google.golang.org/api/googleapi"
|
|
|
|
+
|
|
|
|
+ "github.com/jacobsa/fuse"
|
|
|
|
+ "github.com/jacobsa/fuse/fuseops"
|
|
|
|
+ "github.com/jacobsa/fuse/fuseutil"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+var (
|
|
|
|
+ log = prettylog.New("gdrivemount")
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+type FileHandle struct {
|
|
|
|
+ entry *FileEntry
|
|
|
|
+ tempFile *os.File
|
|
|
|
+ uploadOnDone bool
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*type DirEntry struct {
|
|
|
|
+ file *FileEntry
|
|
|
|
+}*/
|
|
|
|
+
|
|
|
|
+type GDriveFS struct {
|
|
|
|
+ fuseutil.NotImplementedFileSystem
|
|
|
|
+ srv *drive.Service
|
|
|
|
+
|
|
|
|
+ osuser *user.User
|
|
|
|
+ root *FileEntry // hiearchy reference
|
|
|
|
+ //fileList []*FileEntry // All files
|
|
|
|
+
|
|
|
|
+ fileHandles map[fuseops.HandleID]*FileHandle
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+////////////////////////////////////////////////////////
|
|
|
|
+// TOOLS & HELPERS
|
|
|
|
+////////////////////////////////////////////////////////
|
|
|
|
+
|
|
|
|
+// Load append whatever?
|
|
|
|
+func (fs *GDriveFS) appendFile(f *drive.File) *FileEntry {
|
|
|
|
+
|
|
|
|
+ fil := fs.root.findByGID(f.Id, true)
|
|
|
|
+ if fil != nil { // ignore existing ID
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ name := f.Name
|
|
|
|
+ for count := 1; ; {
|
|
|
|
+ found := fs.root.findByName(name, true)
|
|
|
|
+ if found == nil {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ count++
|
|
|
|
+ name = fmt.Sprintf("(%d) %s", count, f.Name)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //fs.fileList = append(fs.fileList, entry) // Globally add in list
|
|
|
|
+
|
|
|
|
+ var entry *FileEntry
|
|
|
|
+ if len(f.Parents) == 0 {
|
|
|
|
+ entry = fs.root.appendGFile(f) // = append(fs.root.fileList, entry)
|
|
|
|
+ }
|
|
|
|
+ for _, parent := range f.Parents { // hierarchy add
|
|
|
|
+ parentEntry := fs.root.findByGID(parent, true)
|
|
|
|
+ if parentEntry == nil {
|
|
|
|
+ log.Fatalln("Non existent parent", parent)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Here
|
|
|
|
+ entry = parentEntry.appendGFile(f)
|
|
|
|
+ if parentEntry.Name == "work" {
|
|
|
|
+ log.Println("Adding file to: ", f.Name, "Count:", len(parentEntry.fileList))
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ //fs.findByGID(f.Parents
|
|
|
|
+ //fs.root.fileList = append(fs.root.fileList, entry)
|
|
|
|
+ return entry
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) defaultAttributes() fuseops.InodeAttributes {
|
|
|
|
+ uid, err := strconv.Atoi(fs.osuser.Uid)
|
|
|
|
+ if err != nil {
|
|
|
|
+ panic("Cannot convert user to int?")
|
|
|
|
+ }
|
|
|
|
+ gid, err := strconv.Atoi(fs.osuser.Gid)
|
|
|
|
+ if err != nil {
|
|
|
|
+ panic("Cannot convert gid to int?")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return fuseops.InodeAttributes{
|
|
|
|
+ Nlink: 1,
|
|
|
|
+ Mode: 0644, // default regular file
|
|
|
|
+ Uid: uint32(uid),
|
|
|
|
+ Gid: uint32(gid),
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) findUnusedHandle() fuseops.HandleID {
|
|
|
|
+
|
|
|
|
+ var handle fuseops.HandleID
|
|
|
|
+
|
|
|
|
+ for handle = 1; handle < 99999; handle++ {
|
|
|
|
+ _, ok := fs.fileHandles[handle]
|
|
|
|
+ if !ok {
|
|
|
|
+ return handle
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return 0 // ERR
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) findUnusedInode() fuseops.InodeID {
|
|
|
|
+ var inode fuseops.InodeID
|
|
|
|
+ for inode = 2; inode < 99999; inode++ {
|
|
|
|
+ f := fs.root.findByInode(inode, true)
|
|
|
|
+ if f == nil {
|
|
|
|
+ return inode
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return 0
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+func (fs *GDriveFS) findByName(name string) *FileEntry {
|
|
|
|
+ // Split path maybe
|
|
|
|
+ for _, v := range fs.fileList {
|
|
|
|
+ if v.Name == name {
|
|
|
|
+ return v
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+func (fs *GDriveFS) findByGID(id string) *FileEntry {
|
|
|
|
+ for _, v := range fs.fileList {
|
|
|
|
+ if v.GFile.Id == id {
|
|
|
|
+ return v
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+func (fs *GDriveFS) findByInode(inode fuseops.InodeID) *FileEntry {
|
|
|
|
+ if inode == 1 {
|
|
|
|
+ return fs.root
|
|
|
|
+ }
|
|
|
|
+ for _, v := range fs.fileList {
|
|
|
|
+ if v.Inode == inode {
|
|
|
|
+ return v
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}*/
|
|
|
|
+
|
|
|
|
+func NewGDriveFS() *GDriveFS {
|
|
|
|
+
|
|
|
|
+ osuser, err := user.Current()
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Fatalf("Unable to fetch current user:", err)
|
|
|
|
+ }
|
|
|
|
+ fs := &GDriveFS{}
|
|
|
|
+ fs.srv = GetDrive()
|
|
|
|
+ fs.root = &FileEntry{
|
|
|
|
+ fs: fs,
|
|
|
|
+ GFile: nil,
|
|
|
|
+ Inode: fuseops.RootInodeID,
|
|
|
|
+ Name: "",
|
|
|
|
+ //fileMap: map[string]*FileEntry{},
|
|
|
|
+ fileList: []*FileEntry{},
|
|
|
|
+ isDir: true,
|
|
|
|
+ }
|
|
|
|
+ fs.fileHandles = map[fuseops.HandleID]*FileHandle{}
|
|
|
|
+ fs.osuser = osuser
|
|
|
|
+
|
|
|
|
+ fs.root.appendGFile(&drive.File{Id: "0", Name: "Loading..."})
|
|
|
|
+
|
|
|
|
+ go fs.refresh() // async fetch
|
|
|
|
+
|
|
|
|
+ return fs
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Reload service files
|
|
|
|
+func (fs *GDriveFS) refresh() {
|
|
|
|
+ fileMap := map[string]*drive.File{} // Temporary map by google drive ID
|
|
|
|
+
|
|
|
|
+ gdFields := googleapi.Field("nextPageToken, files(id,name,size,mimeType,parents,createdTime,modifiedTime)")
|
|
|
|
+
|
|
|
|
+ log.Println("Loading file entries from gdrive")
|
|
|
|
+ r, err := fs.srv.Files.List().PageSize(1000).
|
|
|
|
+ //SupportsTeamDrives(true).
|
|
|
|
+ //IncludeTeamDriveItems(true).
|
|
|
|
+ Fields(gdFields).
|
|
|
|
+ //IncludeTeamDriveItems(true).
|
|
|
|
+ Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ panic(err)
|
|
|
|
+ }
|
|
|
|
+ log.Println("Loaded:", len(r.Files))
|
|
|
|
+
|
|
|
|
+ for _, f := range r.Files {
|
|
|
|
+ fileMap[f.Id] = f
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Rest of the page
|
|
|
|
+ for r.NextPageToken != "" {
|
|
|
|
+ log.Println("Loading next page")
|
|
|
|
+ r, err = fs.srv.Files.List().
|
|
|
|
+ // SupportsTeamDrives(true).
|
|
|
|
+ // IncludeTeamDriveItems(true).
|
|
|
|
+ PageToken(r.NextPageToken).
|
|
|
|
+ //Fields("nextPageToken, files(id,name,size,mimeType,parents,createdTime)").
|
|
|
|
+ Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ panic(err)
|
|
|
|
+ }
|
|
|
|
+ for _, f := range r.Files {
|
|
|
|
+ fileMap[f.Id] = f
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ log.Println("Total entries:", len(fileMap))
|
|
|
|
+
|
|
|
|
+ if err != nil || r == nil {
|
|
|
|
+ log.Fatal("Unable to retrieve files", err)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ fs.root.fileList = []*FileEntry{} // clear
|
|
|
|
+ // Everything loaded we add to our entries
|
|
|
|
+ var appendParentOf func(f *drive.File)
|
|
|
|
+ appendParentOf = func(f *drive.File) {
|
|
|
|
+ for _, pID := range f.Parents {
|
|
|
|
+ parentFile, ok := fileMap[pID]
|
|
|
|
+ if !ok {
|
|
|
|
+ parentFile, err = fs.srv.Files.Get(pID).Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ panic(err)
|
|
|
|
+ }
|
|
|
|
+ fileMap[parentFile.Id] = parentFile
|
|
|
|
+ }
|
|
|
|
+ appendParentOf(parentFile) // Recurse
|
|
|
|
+ fs.appendFile(parentFile)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, f := range fileMap {
|
|
|
|
+ appendParentOf(f) // Check parent first
|
|
|
|
+ fs.appendFile(f)
|
|
|
|
+ }
|
|
|
|
+ log.Println("Refresh done")
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// OpenDir return nil error allows open dir
|
|
|
|
+func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
|
|
|
|
+
|
|
|
|
+ entry := fs.root.findByInode(op.Inode, true)
|
|
|
|
+
|
|
|
|
+ if entry == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ log.Println("Found dir:", entry.Name)
|
|
|
|
+ log.Println("Count of entries:", len(entry.fileList))
|
|
|
|
+ handle := fs.findUnusedHandle()
|
|
|
|
+
|
|
|
|
+ fs.fileHandles[handle] = &FileHandle{entry: entry, tempFile: nil, uploadOnDone: false}
|
|
|
|
+ op.Handle = handle
|
|
|
|
+
|
|
|
|
+ return // No error allow, dir open
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// ReadDir lists files into readdirop
|
|
|
|
+func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
|
|
|
|
+ if op.Inode == 1 {
|
|
|
|
+ log.Println("Reading root inode, offset:", op.Offset)
|
|
|
|
+ }
|
|
|
|
+ dir, ok := fs.fileHandles[op.Handle]
|
|
|
|
+ if !ok {
|
|
|
|
+ log.Fatal("Handle does not exists")
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ log.Println("Dir:", dir)
|
|
|
|
+ entries := []*FileEntry{}
|
|
|
|
+ for _, v := range dir.entry.fileList {
|
|
|
|
+ entries = append(entries, v)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if op.Offset > fuseops.DirOffset(len(entries)) {
|
|
|
|
+ err = fuse.EIO
|
|
|
|
+ log.Println("Err in offset")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ entries = entries[op.Offset:]
|
|
|
|
+
|
|
|
|
+ // Resume at the specified offset into the array.
|
|
|
|
+ for i, v := range entries {
|
|
|
|
+ fusetype := fuseutil.DT_File
|
|
|
|
+ if v.isDir {
|
|
|
|
+ fusetype = fuseutil.DT_Directory
|
|
|
|
+ }
|
|
|
|
+ dirEnt := fuseutil.Dirent{
|
|
|
|
+ Inode: v.Inode,
|
|
|
|
+ Name: v.Name,
|
|
|
|
+ Type: fusetype,
|
|
|
|
+ Offset: fuseops.DirOffset(op.Offset + fuseops.DirOffset(i+1)),
|
|
|
|
+ }
|
|
|
|
+ //log.Println("Entry offset:", dirEnt.Offset)
|
|
|
|
+ n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], dirEnt)
|
|
|
|
+ if n == 0 {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ op.BytesRead += n
|
|
|
|
+ }
|
|
|
|
+ log.Println("Readed:", op.BytesRead, "bytes")
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// We dont do nothing on gdrive for now
|
|
|
|
+func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//GetInodeAttributes return attributes
|
|
|
|
+func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
|
|
|
|
+ log.Println("Loading inode attr for inod:", op.Inode)
|
|
|
|
+ now := time.Now()
|
|
|
|
+ var attr fuseops.InodeAttributes
|
|
|
|
+ if op.Inode == fuseops.RootInodeID {
|
|
|
|
+ attr = fs.defaultAttributes()
|
|
|
|
+ attr.Mode = 0755 | os.ModeDir
|
|
|
|
+ attr.Atime = time.Now()
|
|
|
|
+ attr.Mtime = time.Now()
|
|
|
|
+ attr.Crtime = time.Now()
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ f := fs.root.findByInode(op.Inode, true)
|
|
|
|
+ attr = f.Attr
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ op.Attributes = attr
|
|
|
|
+ op.AttributesExpiration = now.Add(3 * time.Second)
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
|
|
|
|
+ delete(fs.fileHandles, op.Handle)
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
|
|
|
|
+ log.Println("Lookup for:", op.Name, "in inode:", op.Parent)
|
|
|
|
+
|
|
|
|
+ parent := fs.root.findByInode(op.Parent, true)
|
|
|
|
+ now := time.Now()
|
|
|
|
+
|
|
|
|
+ if parent == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
|
|
+ }
|
|
|
|
+ // Transverse all?
|
|
|
|
+ f := parent.findByName(op.Name, false)
|
|
|
|
+ if f == nil {
|
|
|
|
+ err = fuse.ENOENT
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ op.Entry = fuseops.ChildInodeEntry{
|
|
|
|
+ Attributes: f.Attr,
|
|
|
|
+ Child: f.Inode,
|
|
|
|
+ AttributesExpiration: now.Add(1 * time.Hour),
|
|
|
|
+ EntryExpiration: now.Add(1 * time.Hour),
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
|
|
|
|
+ err = fuse.ENOATTR
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//////////////////////////////////////////////////////////////////////////
|
|
|
|
+// File OPS
|
|
|
|
+//////////////////////////////////////////////////////////////////////////
|
|
|
|
+func (fs *GDriveFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
|
|
|
|
+ log.Println("Reading inode:", op.Inode)
|
|
|
|
+ f := fs.root.findByInode(op.Inode, true) // might not exists
|
|
|
|
+
|
|
|
|
+ log.Println("Opening file:", f.Name, " with flags:")
|
|
|
|
+
|
|
|
|
+ // Generate new handle
|
|
|
|
+ handleID := fs.findUnusedHandle()
|
|
|
|
+ fs.fileHandles[handleID] = &FileHandle{entry: f}
|
|
|
|
+ op.Handle = handleID
|
|
|
|
+ op.UseDirectIO = true
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
|
|
|
|
+
|
|
|
|
+ log.Println("Reading from:", op.Offset)
|
|
|
|
+
|
|
|
|
+ lf := fs.fileHandles[op.Handle]
|
|
|
|
+
|
|
|
|
+ if lf.tempFile == nil {
|
|
|
|
+ // Do this on read actually
|
|
|
|
+ var res *http.Response
|
|
|
|
+
|
|
|
|
+ // Export GDocs
|
|
|
|
+ switch lf.entry.GFile.MimeType {
|
|
|
|
+ case "application/vnd.google-apps.document":
|
|
|
|
+ log.Println("Exporting as: text/markdown")
|
|
|
|
+ res, err = fs.srv.Files.Export(lf.entry.GFile.Id, "text/plain").Download()
|
|
|
|
+ case "application/vnd.google-apps.spreadsheet":
|
|
|
|
+ log.Println("Exporting as: text/csv")
|
|
|
|
+ res, err = fs.srv.Files.Export(lf.entry.GFile.Id, "text/csv").Download()
|
|
|
|
+ default:
|
|
|
|
+ res, err = fs.srv.Files.Get(lf.entry.GFile.Id).Download()
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("MimeType:", lf.entry.GFile.MimeType)
|
|
|
|
+ log.Println("Error from GDrive API", err)
|
|
|
|
+ err = fuse.EINVAL
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ defer res.Body.Close()
|
|
|
|
+
|
|
|
|
+ // Local copy
|
|
|
|
+ lf.tempFile, err = ioutil.TempFile(os.TempDir(), "gdfs")
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("Error creating temp file")
|
|
|
|
+ return fuse.ENOSYS
|
|
|
|
+ }
|
|
|
|
+ io.Copy(lf.tempFile, res.Body)
|
|
|
|
+
|
|
|
|
+ lf.tempFile.Seek(0, io.SeekStart)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Somewhat wrong to read file to temp everytime
|
|
|
|
+ op.BytesRead, err = lf.tempFile.ReadAt(op.Dst, op.Offset)
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("Err reading file", err)
|
|
|
|
+ }
|
|
|
|
+ if err == io.EOF {
|
|
|
|
+ err = nil
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
|
|
|
|
+
|
|
|
|
+ parentFile := fs.root.findByInode(op.Parent, true)
|
|
|
|
+ if parentFile == nil {
|
|
|
|
+ err = fuse.ENOENT
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ // Only write on My Drive for now
|
|
|
|
+ if parentFile.Name != "My Drive" || parentFile.Inode == fuseops.RootInodeID {
|
|
|
|
+ log.Println("Parent:", parentFile.Name)
|
|
|
|
+ err = fuse.ENOATTR
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Generate ID
|
|
|
|
+ //genId, err := fs.srv.Files.GenerateIds().Count(1).Do()
|
|
|
|
+ //id := genId.Ids[0]
|
|
|
|
+ parents := []string{parentFile.GFile.Id}
|
|
|
|
+ newFile := &drive.File{
|
|
|
|
+ Parents: parents,
|
|
|
|
+ Name: op.Name,
|
|
|
|
+ }
|
|
|
|
+ createdFile, err := fs.srv.Files.Create(newFile).Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ err = fuse.EINVAL
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ entry := parentFile.appendGFile(createdFile) // Add new created file
|
|
|
|
+ if entry == nil {
|
|
|
|
+ err = fuse.EINVAL
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ log.Println("Created file with inode:", entry.Inode)
|
|
|
|
+
|
|
|
|
+ // Associate a temp file to a new handle
|
|
|
|
+ // Local copy
|
|
|
|
+ localFile, err := ioutil.TempFile(os.TempDir(), "gdfs")
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("Error creating temp file")
|
|
|
|
+ err = fuse.ENOSYS
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ // Lock
|
|
|
|
+ handleID := fs.findUnusedHandle()
|
|
|
|
+ fs.fileHandles[handleID] = &FileHandle{entry, localFile, true}
|
|
|
|
+ //
|
|
|
|
+ op.Handle = handleID
|
|
|
|
+ op.Entry = fuseops.ChildInodeEntry{
|
|
|
|
+ Attributes: entry.Attr,
|
|
|
|
+ Child: entry.Inode,
|
|
|
|
+ AttributesExpiration: time.Now().Add(1 * time.Minute),
|
|
|
|
+ EntryExpiration: time.Now().Add(time.Minute),
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
|
|
|
|
+ lf, ok := fs.fileHandles[op.Handle]
|
|
|
|
+ if !ok {
|
|
|
|
+ err = fuse.EIO
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if lf.tempFile == nil {
|
|
|
|
+ // Local copy
|
|
|
|
+ lf.tempFile, err = ioutil.TempFile(os.TempDir(), "gdfs")
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("Error creating temp file")
|
|
|
|
+ return fuse.ENOSYS
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ _, err = lf.tempFile.WriteAt(op.Data, op.Offset)
|
|
|
|
+ if err != nil {
|
|
|
|
+ err = fuse.EIO
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ lf.uploadOnDone = true
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
|
|
|
|
+ lf := fs.fileHandles[op.Handle]
|
|
|
|
+
|
|
|
|
+ if lf.uploadOnDone {
|
|
|
|
+ lf.tempFile.Sync()
|
|
|
|
+ lf.tempFile.Seek(0, io.SeekStart)
|
|
|
|
+ // Upload somehow
|
|
|
|
+ ngfile := &drive.File{}
|
|
|
|
+ up := fs.srv.Files.Update(lf.entry.GFile.Id, ngfile)
|
|
|
|
+ if err != nil {
|
|
|
|
+ err = fuse.EINVAL
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ updatedFile, err := up.Media(lf.tempFile).Do()
|
|
|
|
+ //updatedFile, err := up.ResumableMedia(ctx, lf.tempFile, tstat.Size(), "text/txt").Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ log.Println("Err on Media:", err)
|
|
|
|
+ return fuse.EIO
|
|
|
|
+ }
|
|
|
|
+ log.Println("What to do with this?:", updatedFile)
|
|
|
|
+ }
|
|
|
|
+ if lf.tempFile != nil {
|
|
|
|
+ lf.tempFile.Close()
|
|
|
|
+ os.Remove(lf.tempFile.Name())
|
|
|
|
+ }
|
|
|
|
+ delete(fs.fileHandles, op.Handle)
|
|
|
|
+
|
|
|
|
+ //go fs.refresh()
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
|
|
|
|
+ parentEntry := fs.root.findByInode(op.Parent, true)
|
|
|
|
+ if parentEntry == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
|
|
+ }
|
|
|
|
+ fileEntry := parentEntry.findByName(op.Name, false)
|
|
|
|
+ if fileEntry == nil {
|
|
|
|
+ return fuse.ENOATTR
|
|
|
|
+ }
|
|
|
|
+ err = fs.srv.Files.Delete(fileEntry.GFile.Id).Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return fuse.EIO
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ toremove := -1
|
|
|
|
+ for k, v := range parentEntry.fileList {
|
|
|
|
+ if v == fileEntry {
|
|
|
|
+ toremove = k
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if toremove != -1 {
|
|
|
|
+ parentEntry.fileList = append(parentEntry.fileList[:toremove], parentEntry.fileList[toremove+1:]...)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
|
|
|
|
+ return
|
|
|
|
+}
|