|
@@ -1,10 +1,8 @@
|
|
|
|
+// gdrivemount implements a google drive fuse driver
|
|
package gdrivemount
|
|
package gdrivemount
|
|
|
|
|
|
import (
|
|
import (
|
|
- "fmt"
|
|
|
|
"io"
|
|
"io"
|
|
- "io/ioutil"
|
|
|
|
- "net/http"
|
|
|
|
"os"
|
|
"os"
|
|
"os/user"
|
|
"os/user"
|
|
"strconv"
|
|
"strconv"
|
|
@@ -26,54 +24,50 @@ var (
|
|
log = prettylog.New("gdrivemount")
|
|
log = prettylog.New("gdrivemount")
|
|
)
|
|
)
|
|
|
|
|
|
-type FileHandle struct {
|
|
|
|
- entry *FileEntry
|
|
|
|
- tempFile *os.File
|
|
|
|
|
|
+type fileHandle struct {
|
|
|
|
+ handleID fuseops.HandleID
|
|
|
|
+ entry *FileEntry
|
|
|
|
+ //tempFile *os.File
|
|
uploadOnDone bool
|
|
uploadOnDone bool
|
|
|
|
+ // Testing
|
|
|
|
+ entries []fuseutil.Dirent
|
|
|
|
+ buf []byte
|
|
}
|
|
}
|
|
|
|
|
|
/*type DirEntry struct {
|
|
/*type DirEntry struct {
|
|
file *FileEntry
|
|
file *FileEntry
|
|
}*/
|
|
}*/
|
|
|
|
|
|
|
|
+// GDriveFS handler
|
|
type GDriveFS struct {
|
|
type GDriveFS struct {
|
|
fuseutil.NotImplementedFileSystem
|
|
fuseutil.NotImplementedFileSystem
|
|
srv *drive.Service
|
|
srv *drive.Service
|
|
|
|
|
|
osuser *user.User
|
|
osuser *user.User
|
|
root *FileEntry // hiearchy reference
|
|
root *FileEntry // hiearchy reference
|
|
- //fileList []*FileEntry // All files
|
|
|
|
|
|
|
|
- fileHandles map[fuseops.HandleID]*FileHandle
|
|
|
|
|
|
+ fileHandles map[fuseops.HandleID]*fileHandle
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////
|
|
// TOOLS & HELPERS
|
|
// TOOLS & HELPERS
|
|
////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////
|
|
|
|
|
|
-// Load append whatever?
|
|
|
|
|
|
+// appendFile solve parent before add,
|
|
func (fs *GDriveFS) appendFile(f *drive.File) *FileEntry {
|
|
func (fs *GDriveFS) appendFile(f *drive.File) *FileEntry {
|
|
|
|
|
|
fil := fs.root.findByGID(f.Id, true)
|
|
fil := fs.root.findByGID(f.Id, true)
|
|
if fil != nil { // ignore existing ID
|
|
if fil != nil { // ignore existing ID
|
|
return nil
|
|
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
|
|
var entry *FileEntry
|
|
if len(f.Parents) == 0 {
|
|
if len(f.Parents) == 0 {
|
|
entry = fs.root.appendGFile(f) // = append(fs.root.fileList, entry)
|
|
entry = fs.root.appendGFile(f) // = append(fs.root.fileList, entry)
|
|
}
|
|
}
|
|
|
|
+ if len(f.Parents) > 1 {
|
|
|
|
+ log.Println("This one has more than 1 parent:", f.Parents)
|
|
|
|
+ }
|
|
for _, parent := range f.Parents { // hierarchy add
|
|
for _, parent := range f.Parents { // hierarchy add
|
|
parentEntry := fs.root.findByGID(parent, true)
|
|
parentEntry := fs.root.findByGID(parent, true)
|
|
if parentEntry == nil {
|
|
if parentEntry == nil {
|
|
@@ -82,13 +76,7 @@ func (fs *GDriveFS) appendFile(f *drive.File) *FileEntry {
|
|
|
|
|
|
// Here
|
|
// Here
|
|
entry = parentEntry.appendGFile(f)
|
|
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
|
|
return entry
|
|
}
|
|
}
|
|
|
|
|
|
@@ -110,18 +98,21 @@ func (fs *GDriveFS) defaultAttributes() fuseops.InodeAttributes {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-func (fs *GDriveFS) findUnusedHandle() fuseops.HandleID {
|
|
|
|
|
|
+func (fs *GDriveFS) findUnusedHandle() *fileHandle {
|
|
|
|
+ // Lock here instead
|
|
|
|
|
|
var handle fuseops.HandleID
|
|
var handle fuseops.HandleID
|
|
|
|
|
|
for handle = 1; handle < 99999; handle++ {
|
|
for handle = 1; handle < 99999; handle++ {
|
|
_, ok := fs.fileHandles[handle]
|
|
_, ok := fs.fileHandles[handle]
|
|
if !ok {
|
|
if !ok {
|
|
- return handle
|
|
|
|
|
|
+ break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ fh := &fileHandle{handleID: handle}
|
|
|
|
+ fs.fileHandles[handle] = fh
|
|
|
|
|
|
- return 0 // ERR
|
|
|
|
|
|
+ return fh
|
|
}
|
|
}
|
|
|
|
|
|
func (fs *GDriveFS) findUnusedInode() fuseops.InodeID {
|
|
func (fs *GDriveFS) findUnusedInode() fuseops.InodeID {
|
|
@@ -132,38 +123,13 @@ func (fs *GDriveFS) findUnusedInode() fuseops.InodeID {
|
|
return inode
|
|
return inode
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return 0
|
|
|
|
-}
|
|
|
|
|
|
+ if inode == 4 {
|
|
|
|
+ log.Println("Inode is 4")
|
|
|
|
|
|
-/*
|
|
|
|
-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
|
|
|
|
|
|
+ log.Println("0 Inode ODD")
|
|
|
|
+ return 0
|
|
}
|
|
}
|
|
-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 {
|
|
func NewGDriveFS() *GDriveFS {
|
|
|
|
|
|
@@ -172,9 +138,17 @@ func NewGDriveFS() *GDriveFS {
|
|
log.Fatalf("Unable to fetch current user:", err)
|
|
log.Fatalf("Unable to fetch current user:", err)
|
|
}
|
|
}
|
|
fs := &GDriveFS{}
|
|
fs := &GDriveFS{}
|
|
- fs.srv = GetDrive()
|
|
|
|
|
|
+ fs.osuser = osuser
|
|
|
|
+ fs.srv = GetDriveService()
|
|
fs.root = &FileEntry{
|
|
fs.root = &FileEntry{
|
|
- fs: fs,
|
|
|
|
|
|
+ fs: fs,
|
|
|
|
+ Attr: fuseops.InodeAttributes{
|
|
|
|
+ Mode: os.FileMode(0755) | os.ModeDir,
|
|
|
|
+ Nlink: 1,
|
|
|
|
+ Size: 4096,
|
|
|
|
+ Uid: fs.getUID(),
|
|
|
|
+ Gid: fs.getGID(),
|
|
|
|
+ },
|
|
GFile: nil,
|
|
GFile: nil,
|
|
Inode: fuseops.RootInodeID,
|
|
Inode: fuseops.RootInodeID,
|
|
Name: "",
|
|
Name: "",
|
|
@@ -182,28 +156,36 @@ func NewGDriveFS() *GDriveFS {
|
|
fileList: []*FileEntry{},
|
|
fileList: []*FileEntry{},
|
|
isDir: true,
|
|
isDir: true,
|
|
}
|
|
}
|
|
- fs.fileHandles = map[fuseops.HandleID]*FileHandle{}
|
|
|
|
- fs.osuser = osuser
|
|
|
|
|
|
+ fs.fileHandles = map[fuseops.HandleID]*fileHandle{}
|
|
|
|
|
|
- fs.root.appendGFile(&drive.File{Id: "0", Name: "Loading..."})
|
|
|
|
|
|
+ entry := fs.root.appendGFile(&drive.File{Id: "0", Name: "Loading..."})
|
|
|
|
+ entry.Attr.Mode = os.FileMode(0)
|
|
|
|
|
|
go fs.refresh() // async fetch
|
|
go fs.refresh() // async fetch
|
|
|
|
|
|
return fs
|
|
return fs
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Cache somewhere
|
|
|
|
+func (fs *GDriveFS) getUID() uint32 {
|
|
|
|
+ uid, _ := strconv.Atoi(fs.osuser.Uid)
|
|
|
|
+ return uint32(uid)
|
|
|
|
+}
|
|
|
|
+func (fs *GDriveFS) getGID() uint32 {
|
|
|
|
+ gid, _ := strconv.Atoi(fs.osuser.Gid)
|
|
|
|
+ return uint32(gid)
|
|
|
|
+}
|
|
|
|
+
|
|
// Reload service files
|
|
// Reload service files
|
|
func (fs *GDriveFS) refresh() {
|
|
func (fs *GDriveFS) refresh() {
|
|
fileMap := map[string]*drive.File{} // Temporary map by google drive ID
|
|
fileMap := map[string]*drive.File{} // Temporary map by google drive ID
|
|
|
|
|
|
gdFields := googleapi.Field("nextPageToken, files(id,name,size,mimeType,parents,createdTime,modifiedTime)")
|
|
gdFields := googleapi.Field("nextPageToken, files(id,name,size,mimeType,parents,createdTime,modifiedTime)")
|
|
-
|
|
|
|
log.Println("Loading file entries from gdrive")
|
|
log.Println("Loading file entries from gdrive")
|
|
r, err := fs.srv.Files.List().PageSize(1000).
|
|
r, err := fs.srv.Files.List().PageSize(1000).
|
|
- //SupportsTeamDrives(true).
|
|
|
|
- //IncludeTeamDriveItems(true).
|
|
|
|
|
|
+ SupportsTeamDrives(true).
|
|
|
|
+ IncludeTeamDriveItems(true).
|
|
Fields(gdFields).
|
|
Fields(gdFields).
|
|
- //IncludeTeamDriveItems(true).
|
|
|
|
Do()
|
|
Do()
|
|
if err != nil {
|
|
if err != nil {
|
|
panic(err)
|
|
panic(err)
|
|
@@ -217,12 +199,7 @@ func (fs *GDriveFS) refresh() {
|
|
// Rest of the page
|
|
// Rest of the page
|
|
for r.NextPageToken != "" {
|
|
for r.NextPageToken != "" {
|
|
log.Println("Loading next page")
|
|
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()
|
|
|
|
|
|
+ r, err = fs.srv.Files.List().PageToken(r.NextPageToken).Do()
|
|
if err != nil {
|
|
if err != nil {
|
|
panic(err)
|
|
panic(err)
|
|
}
|
|
}
|
|
@@ -236,7 +213,8 @@ func (fs *GDriveFS) refresh() {
|
|
log.Fatal("Unable to retrieve files", err)
|
|
log.Fatal("Unable to retrieve files", err)
|
|
}
|
|
}
|
|
|
|
|
|
- fs.root.fileList = []*FileEntry{} // clear
|
|
|
|
|
|
+ fs.root.fileList = []*FileEntry{} // clear fileList
|
|
|
|
+ // Helper func to recurse
|
|
// Everything loaded we add to our entries
|
|
// Everything loaded we add to our entries
|
|
var appendParentOf func(f *drive.File)
|
|
var appendParentOf func(f *drive.File)
|
|
appendParentOf = func(f *drive.File) {
|
|
appendParentOf = func(f *drive.File) {
|
|
@@ -259,50 +237,94 @@ func (fs *GDriveFS) refresh() {
|
|
fs.appendFile(f)
|
|
fs.appendFile(f)
|
|
}
|
|
}
|
|
log.Println("Refresh done")
|
|
log.Println("Refresh done")
|
|
|
|
+
|
|
|
|
+ log.Println("File count:", fs.root.count())
|
|
}
|
|
}
|
|
|
|
|
|
// OpenDir return nil error allows open dir
|
|
// OpenDir return nil error allows open dir
|
|
func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
|
|
func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
|
|
|
|
|
|
entry := fs.root.findByInode(op.Inode, true)
|
|
entry := fs.root.findByInode(op.Inode, true)
|
|
-
|
|
|
|
if entry == nil {
|
|
if entry == nil {
|
|
return fuse.ENOENT
|
|
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
|
|
|
|
|
|
+ fh := fs.findUnusedHandle()
|
|
|
|
+ fh.entry = entry
|
|
|
|
+ op.Handle = fh.handleID
|
|
|
|
|
|
return // No error allow, dir open
|
|
return // No error allow, dir open
|
|
}
|
|
}
|
|
|
|
|
|
// ReadDir lists files into readdirop
|
|
// ReadDir lists files into readdirop
|
|
func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
|
|
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]
|
|
|
|
|
|
+ fh, ok := fs.fileHandles[op.Handle]
|
|
if !ok {
|
|
if !ok {
|
|
log.Fatal("Handle does not exists")
|
|
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 == 0 { // Rebuild/rewind dir list
|
|
|
|
+ fh.entries = []fuseutil.Dirent{}
|
|
|
|
+
|
|
|
|
+ //fh.buf = make([]byte, 1000000) // Temp bigbuf somehow
|
|
|
|
+ //written := 0
|
|
|
|
+
|
|
|
|
+ for i, v := range fh.entry.fileList {
|
|
|
|
+ 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(i) + 1,
|
|
|
|
+ }
|
|
|
|
+ // written += fuseutil.WriteDirent(fh.buf[written:], dirEnt)
|
|
|
|
+ fh.entries = append(fh.entries, dirEnt)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- if op.Offset > fuseops.DirOffset(len(entries)) {
|
|
|
|
- err = fuse.EIO
|
|
|
|
- log.Println("Err in offset")
|
|
|
|
- return
|
|
|
|
|
|
+ // local Buftest
|
|
|
|
+
|
|
|
|
+ // Raw copy
|
|
|
|
+ /*log.Println("Just copy data off:", op.Offset)
|
|
|
|
+ for {
|
|
|
|
+ n := copy(op.Dst[op.BytesRead:], fh.buf[op.Offset:])
|
|
|
|
+ if n == 0 {
|
|
|
|
+
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ log.Println("Written:", n)
|
|
|
|
+ op.BytesRead += n
|
|
|
|
+ }*/
|
|
|
|
+
|
|
|
|
+ index := int(op.Offset)
|
|
|
|
+ if index > len(fh.entries) {
|
|
|
|
+ return fuse.EINVAL
|
|
|
|
+ }
|
|
|
|
+ if index > 0 {
|
|
|
|
+ index++
|
|
|
|
+ }
|
|
|
|
+ count := 0
|
|
|
|
+ for i := index; i < len(fh.entries); i++ {
|
|
|
|
+ n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
|
|
|
|
+ //log.Println("Written:", n)
|
|
|
|
+ if n == 0 {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ op.BytesRead += n
|
|
|
|
+ count++
|
|
}
|
|
}
|
|
|
|
+ //log.Printf("Written %d Dirent: '%s' TotalSent: %d", count, fh.entry.Name, op.BytesRead)
|
|
|
|
+
|
|
|
|
+ /*log.Println("Sending from offset:", op.Offset)
|
|
entries = entries[op.Offset:]
|
|
entries = entries[op.Offset:]
|
|
|
|
|
|
|
|
+ log.Println("Total entries for this tree:", len(dir.entry.fileList))
|
|
|
|
+
|
|
|
|
+ count := 0
|
|
|
|
+ offCount := int(op.Offset)
|
|
// Resume at the specified offset into the array.
|
|
// Resume at the specified offset into the array.
|
|
for i, v := range entries {
|
|
for i, v := range entries {
|
|
fusetype := fuseutil.DT_File
|
|
fusetype := fuseutil.DT_File
|
|
@@ -317,158 +339,117 @@ func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err err
|
|
}
|
|
}
|
|
//log.Println("Entry offset:", dirEnt.Offset)
|
|
//log.Println("Entry offset:", dirEnt.Offset)
|
|
n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], dirEnt)
|
|
n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], dirEnt)
|
|
- if n == 0 {
|
|
|
|
|
|
+ if n == 0 { // let go
|
|
|
|
+ log.Println("Broke the Write")
|
|
break
|
|
break
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ count++
|
|
|
|
+ offCount++
|
|
op.BytesRead += n
|
|
op.BytesRead += n
|
|
}
|
|
}
|
|
- log.Println("Readed:", op.BytesRead, "bytes")
|
|
|
|
-
|
|
|
|
|
|
+ log.Println("BytesRead:", op.BytesRead, " count:", count, " offCount:", offCount, " last:", len(dir.entry.fileList)-count)*/
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
-// We dont do nothing on gdrive for now
|
|
|
|
|
|
+// SetInodeAttributes Not sure what attributes gdrive support we just leave this blank for now
|
|
func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
|
|
func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
|
|
-
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
//GetInodeAttributes return attributes
|
|
//GetInodeAttributes return attributes
|
|
func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
|
|
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
|
|
|
|
|
|
+ f := fs.root.findByInode(op.Inode, true)
|
|
|
|
+ if f == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
}
|
|
}
|
|
-
|
|
|
|
- op.Attributes = attr
|
|
|
|
- op.AttributesExpiration = now.Add(3 * time.Second)
|
|
|
|
|
|
+ op.Attributes = f.Attr
|
|
|
|
+ op.AttributesExpiration = time.Now().Add(time.Minute)
|
|
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+// ReleaseDirHandle deletes file handle entry
|
|
func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
|
|
func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
|
|
delete(fs.fileHandles, op.Handle)
|
|
delete(fs.fileHandles, op.Handle)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// LookUpInode based on Parent and Name we return a self cached inode
|
|
func (fs *GDriveFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
|
|
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 {
|
|
|
|
|
|
+ parentFile := fs.root.findByInode(op.Parent, true) // true means transverse all
|
|
|
|
+ if parentFile == nil {
|
|
return fuse.ENOENT
|
|
return fuse.ENOENT
|
|
}
|
|
}
|
|
- // Transverse all?
|
|
|
|
- f := parent.findByName(op.Name, false)
|
|
|
|
|
|
+
|
|
|
|
+ now := time.Now()
|
|
|
|
+ // Transverse only local
|
|
|
|
+ f := parentFile.findByName(op.Name, false)
|
|
if f == nil {
|
|
if f == nil {
|
|
- err = fuse.ENOENT
|
|
|
|
- return
|
|
|
|
|
|
+ return fuse.ENOENT
|
|
}
|
|
}
|
|
|
|
|
|
op.Entry = fuseops.ChildInodeEntry{
|
|
op.Entry = fuseops.ChildInodeEntry{
|
|
Attributes: f.Attr,
|
|
Attributes: f.Attr,
|
|
Child: f.Inode,
|
|
Child: f.Inode,
|
|
- AttributesExpiration: now.Add(1 * time.Hour),
|
|
|
|
- EntryExpiration: now.Add(1 * time.Hour),
|
|
|
|
|
|
+ AttributesExpiration: now.Add(time.Minute),
|
|
|
|
+ EntryExpiration: now.Add(time.Minute),
|
|
}
|
|
}
|
|
-
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
-func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
|
|
|
|
|
|
+// StatFS basically allows StatFS to run
|
|
|
|
+/*func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
|
|
return
|
|
return
|
|
-}
|
|
|
|
|
|
+}*/
|
|
|
|
|
|
|
|
+// ForgetInode allows to forgetInode
|
|
func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
|
|
func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// GetXAttr special attributes
|
|
func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
|
|
func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
|
|
- err = fuse.ENOATTR
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// File OPS
|
|
// File OPS
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
+
|
|
|
|
+// OpenFile creates a temporary handle to be handled on read or write
|
|
func (fs *GDriveFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
|
|
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
|
|
f := fs.root.findByInode(op.Inode, true) // might not exists
|
|
|
|
|
|
- log.Println("Opening file:", f.Name, " with flags:")
|
|
|
|
-
|
|
|
|
// Generate new handle
|
|
// Generate new handle
|
|
- handleID := fs.findUnusedHandle()
|
|
|
|
- fs.fileHandles[handleID] = &FileHandle{entry: f}
|
|
|
|
- op.Handle = handleID
|
|
|
|
|
|
+ fh := fs.findUnusedHandle()
|
|
|
|
+ fh.entry = f
|
|
|
|
+
|
|
|
|
+ op.Handle = fh.handleID
|
|
op.UseDirectIO = true
|
|
op.UseDirectIO = true
|
|
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// ReadFile if the first time we download the google drive file into a local temporary file
|
|
func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
|
|
func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
|
|
-
|
|
|
|
- log.Println("Reading from:", op.Offset)
|
|
|
|
-
|
|
|
|
lf := fs.fileHandles[op.Handle]
|
|
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)
|
|
|
|
|
|
+ localFile := lf.entry.cache()
|
|
|
|
+ op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
|
|
if err != nil {
|
|
if err != nil {
|
|
log.Println("Err reading file", err)
|
|
log.Println("Err reading file", err)
|
|
}
|
|
}
|
|
- if err == io.EOF {
|
|
|
|
|
|
+ if err == io.EOF { // fuse does not expect a EOF
|
|
err = nil
|
|
err = nil
|
|
}
|
|
}
|
|
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+// CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
|
|
func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
|
|
func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
|
|
|
|
|
|
parentFile := fs.root.findByInode(op.Parent, true)
|
|
parentFile := fs.root.findByInode(op.Parent, true)
|
|
@@ -477,8 +458,7 @@ func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (e
|
|
return
|
|
return
|
|
}
|
|
}
|
|
// Only write on My Drive for now
|
|
// Only write on My Drive for now
|
|
- if parentFile.Name != "My Drive" || parentFile.Inode == fuseops.RootInodeID {
|
|
|
|
- log.Println("Parent:", parentFile.Name)
|
|
|
|
|
|
+ if parentFile.Inode == fuseops.RootInodeID {
|
|
err = fuse.ENOATTR
|
|
err = fuse.ENOATTR
|
|
return
|
|
return
|
|
}
|
|
}
|
|
@@ -502,31 +482,33 @@ func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (e
|
|
err = fuse.EINVAL
|
|
err = fuse.EINVAL
|
|
return
|
|
return
|
|
}
|
|
}
|
|
- log.Println("Created file with inode:", entry.Inode)
|
|
|
|
|
|
+ log.Println("File Created", entry)
|
|
|
|
|
|
|
|
+ localFile := entry.cache()
|
|
|
|
+ if localFile == nil {
|
|
|
|
+ return fuse.EINVAL
|
|
|
|
+ }
|
|
// Associate a temp file to a new handle
|
|
// Associate a temp file to a new handle
|
|
// Local copy
|
|
// Local copy
|
|
- localFile, err := ioutil.TempFile(os.TempDir(), "gdfs")
|
|
|
|
- if err != nil {
|
|
|
|
- log.Println("Error creating temp file")
|
|
|
|
- err = fuse.ENOSYS
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
// Lock
|
|
// Lock
|
|
- handleID := fs.findUnusedHandle()
|
|
|
|
- fs.fileHandles[handleID] = &FileHandle{entry, localFile, true}
|
|
|
|
|
|
+ fh := fs.findUnusedHandle()
|
|
|
|
+ fh.entry = entry
|
|
|
|
+ fh.uploadOnDone = true
|
|
//
|
|
//
|
|
- op.Handle = handleID
|
|
|
|
|
|
+ op.Handle = fh.handleID
|
|
op.Entry = fuseops.ChildInodeEntry{
|
|
op.Entry = fuseops.ChildInodeEntry{
|
|
Attributes: entry.Attr,
|
|
Attributes: entry.Attr,
|
|
Child: entry.Inode,
|
|
Child: entry.Inode,
|
|
- AttributesExpiration: time.Now().Add(1 * time.Minute),
|
|
|
|
|
|
+ AttributesExpiration: time.Now().Add(time.Minute),
|
|
EntryExpiration: time.Now().Add(time.Minute),
|
|
EntryExpiration: time.Now().Add(time.Minute),
|
|
}
|
|
}
|
|
|
|
+ op.Mode = entry.Attr.Mode
|
|
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// WriteFile as ReadFile it creates a temporary file on first read
|
|
|
|
+// Maybe the ReadFile should be called here aswell to cache current contents since we are using writeAt
|
|
func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
|
|
func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
|
|
lf, ok := fs.fileHandles[op.Handle]
|
|
lf, ok := fs.fileHandles[op.Handle]
|
|
if !ok {
|
|
if !ok {
|
|
@@ -534,16 +516,12 @@ func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err
|
|
return
|
|
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
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
|
|
+ localFile := lf.entry.cache()
|
|
|
|
+ if localFile == nil {
|
|
|
|
+ return fuse.EINVAL
|
|
}
|
|
}
|
|
- _, err = lf.tempFile.WriteAt(op.Data, op.Offset)
|
|
|
|
|
|
+
|
|
|
|
+ _, err = localFile.WriteAt(op.Data, op.Offset)
|
|
if err != nil {
|
|
if err != nil {
|
|
err = fuse.EIO
|
|
err = fuse.EIO
|
|
return
|
|
return
|
|
@@ -553,36 +531,24 @@ func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
|
|
func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
|
|
func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
|
|
lf := fs.fileHandles[op.Handle]
|
|
lf := fs.fileHandles[op.Handle]
|
|
|
|
|
|
if lf.uploadOnDone {
|
|
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)
|
|
|
|
|
|
+ err = lf.entry.sync()
|
|
if err != nil {
|
|
if err != nil {
|
|
- err = fuse.EINVAL
|
|
|
|
- return
|
|
|
|
|
|
+ return fuse.EINVAL
|
|
}
|
|
}
|
|
- 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())
|
|
|
|
}
|
|
}
|
|
|
|
+ lf.entry.clearCache()
|
|
delete(fs.fileHandles, op.Handle)
|
|
delete(fs.fileHandles, op.Handle)
|
|
|
|
|
|
//go fs.refresh()
|
|
//go fs.refresh()
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+// Unlink remove file and remove from local cache entry
|
|
func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
|
|
func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
|
|
parentEntry := fs.root.findByInode(op.Parent, true)
|
|
parentEntry := fs.root.findByInode(op.Parent, true)
|
|
if parentEntry == nil {
|
|
if parentEntry == nil {
|
|
@@ -597,21 +563,98 @@ func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error
|
|
return fuse.EIO
|
|
return fuse.EIO
|
|
}
|
|
}
|
|
|
|
|
|
- toremove := -1
|
|
|
|
- for k, v := range parentEntry.fileList {
|
|
|
|
- if v == fileEntry {
|
|
|
|
- toremove = k
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
|
|
+ parentEntry.removeEntry(fileEntry)
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// FlushFile just returns no error, maybe upload should be handled here
|
|
|
|
+func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// MkDir creates a directory on a parent dir
|
|
|
|
+func (fs *GDriveFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
|
|
|
|
+
|
|
|
|
+ parentFile := fs.root.findByInode(op.Parent, true)
|
|
|
|
+ if parentFile == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
|
|
+ }
|
|
|
|
+ if parentFile.Inode == fuseops.RootInodeID {
|
|
|
|
+ return fuse.ENOATTR
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Should check existent first too
|
|
|
|
+ fi, err := fs.srv.Files.Create(&drive.File{
|
|
|
|
+ Parents: []string{parentFile.GFile.Id},
|
|
|
|
+ MimeType: "application/vnd.google-apps.folder",
|
|
|
|
+ Name: op.Name,
|
|
|
|
+ }).Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return fuse.ENOATTR
|
|
|
|
+ }
|
|
|
|
+ entry := parentFile.appendGFile(fi)
|
|
|
|
+ if entry == nil {
|
|
|
|
+ return fuse.EINVAL
|
|
}
|
|
}
|
|
|
|
|
|
- if toremove != -1 {
|
|
|
|
- parentEntry.fileList = append(parentEntry.fileList[:toremove], parentEntry.fileList[toremove+1:]...)
|
|
|
|
|
|
+ op.Entry = fuseops.ChildInodeEntry{
|
|
|
|
+ Attributes: entry.Attr,
|
|
|
|
+ Child: entry.Inode,
|
|
|
|
+ AttributesExpiration: time.Now().Add(time.Minute),
|
|
|
|
+ EntryExpiration: time.Now().Add(time.Microsecond),
|
|
}
|
|
}
|
|
|
|
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
|
|
-func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
|
|
|
|
|
|
+func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
|
|
|
|
+
|
|
|
|
+ parentFile := fs.root.findByInode(op.Parent, true)
|
|
|
|
+ if parentFile == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ theFile := parentFile.findByName(op.Name, false)
|
|
|
|
+
|
|
|
|
+ err = fs.srv.Files.Delete(theFile.GFile.Id).Do()
|
|
|
|
+ if err != nil {
|
|
|
|
+ return fuse.ENOTEMPTY
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ parentFile.removeEntry(theFile)
|
|
|
|
+
|
|
|
|
+ // Remove from entry somehow
|
|
|
|
+
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
|
|
|
|
+ oldParentFile := fs.root.findByInode(op.OldParent, true)
|
|
|
|
+ if oldParentFile == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
|
|
+ }
|
|
|
|
+ newParentFile := fs.root.findByInode(op.NewParent, true)
|
|
|
|
+ if newParentFile == nil {
|
|
|
|
+ return fuse.ENOENT
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ oldFile := oldParentFile.findByName(op.OldName, false)
|
|
|
|
+
|
|
|
|
+ ngFile := &drive.File{
|
|
|
|
+ Name: op.NewName,
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ updateCall := fs.srv.Files.Update(oldFile.GFile.Id, ngFile)
|
|
|
|
+ if oldParentFile != newParentFile {
|
|
|
|
+ updateCall.RemoveParents(oldParentFile.GFile.Id)
|
|
|
|
+ updateCall.AddParents(newParentFile.GFile.Id)
|
|
|
|
+ }
|
|
|
|
+ updatedFile, err := updateCall.Do()
|
|
|
|
+
|
|
|
|
+ oldParentFile.removeEntry(oldFile)
|
|
|
|
+ newParentFile.appendGFile(updatedFile)
|
|
|
|
+
|
|
|
|
+ return
|
|
|
|
+
|
|
|
|
+}
|