luis 7 anni fa
commit
44d8cefcfd

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+deps/pkg
+pkg
+bin
+DIST
+

+ 15 - 0
Makefile

@@ -0,0 +1,15 @@
+GOPATH=$(CURDIR)/deps:$(CURDIR)
+DIST=./DIST
+
+all:
+	go build -o DIST/gdrivemount gdrivemount/cmd/gdrivemount
+
+deps:
+	go get -v ./src/gdrivemount/cmd/gdrivemount
+
+test:
+	cd test; ../DIST/gdrivemount mount
+
+clean:
+	rm -rf DIST deps
+

+ 1 - 0
deps/src/cloud.google.com/go

@@ -0,0 +1 @@
+Subproject commit 558b56dfa3c56acc26fef35cb07f97df0bb18b39

+ 1 - 0
deps/src/dev.hexasoftware.com/hxs/prettylog

@@ -0,0 +1 @@
+Subproject commit ede02c9e65b0e987e83398ec549d0088cde1bb23

+ 1 - 0
deps/src/github.com/icattlecoder/godaemon

@@ -0,0 +1 @@
+Subproject commit f0fff2a3c017c9d9d8520e3c5366946e96e9e081

+ 1 - 0
deps/src/github.com/jacobsa/fuse

@@ -0,0 +1 @@
+Subproject commit fe7f3a55dcaa3a8f3d5ff6a85b16b62b7a2c446c

+ 1 - 0
deps/src/golang.org/x/crypto

@@ -0,0 +1 @@
+Subproject commit 69be088f860613049aa58c65154d1b1d32bbdf90

+ 1 - 0
deps/src/golang.org/x/net

@@ -0,0 +1 @@
+Subproject commit 570fa1c91359c1869590e9cedf3b53162a51a167

+ 1 - 0
deps/src/golang.org/x/oauth2

@@ -0,0 +1 @@
+Subproject commit cce311a261e6fcf29de72ca96827bdb0b7d9c9e6

+ 1 - 0
deps/src/google.golang.org/api

@@ -0,0 +1 @@
+Subproject commit e6586c9293b9d514c7f5d5076731ec977cff1be6

+ 1 - 0
env.sh

@@ -0,0 +1 @@
+export GOPATH=$(pwd)/deps:$(pwd)

+ 49 - 0
src/gdrivemount/cmd/gdrivemount/main.go

@@ -0,0 +1,49 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"gdrivemount"
+
+	"github.com/jacobsa/fuse"
+	"github.com/jacobsa/fuse/fuseutil"
+
+	"dev.hexasoftware.com/hxs/prettylog"
+	_ "github.com/icattlecoder/godaemon"
+)
+
+var (
+	log = prettylog.New("main")
+)
+
+func main() {
+	prettylog.Global()
+	// getClient
+	gdriveFS := gdrivemount.NewGDriveFS()
+
+	flag.Parse()
+	if len(flag.Args()) < 1 {
+		log.Fatal("Usage:\n gdrivemount MOUNTPOINT")
+	}
+
+	ctx := context.Background()
+
+	server := fuseutil.NewFileSystemServer(gdriveFS)
+
+	mfs, err := fuse.Mount(flag.Arg(0), server, &fuse.MountConfig{DebugLogger: prettylog.New("fuse"), ErrorLogger: prettylog.New("fuse-err")})
+	if err != nil {
+		log.Fatal("Failed mounting path", flag.Arg(0))
+	}
+	if err := mfs.Join(ctx); err != nil {
+		log.Fatalf("Joining: %v", err)
+	}
+
+	/* OLD
+	nfs := pathfs.NewPathNodeFs(gdriveFS, nil)
+	server, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), nil)
+	if err != nil {
+		log.Fatalf("Mount fail: %v\n", err)
+	}
+
+	server.Serve()*/
+}

+ 132 - 0
src/gdrivemount/fileentry.go

@@ -0,0 +1,132 @@
+package gdrivemount
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/jacobsa/fuse/fuseops"
+	drive "google.golang.org/api/drive/v3"
+)
+
+type FileEntry struct {
+	fs    *GDriveFS
+	GFile *drive.File
+	isDir bool
+	Name  string // local name
+	Inode fuseops.InodeID
+	Attr  fuseops.InodeAttributes
+	// inode?
+	//fileMap map[string]*FileEntry
+	fileList []*FileEntry // children
+	//fileMap map[string]*FileEntry // Children?
+}
+
+// Load append whatever?
+// append file to this tree
+func (fe *FileEntry) appendGFile(f *drive.File) *FileEntry {
+
+	name := f.Name
+	count := 1
+	nameParts := strings.Split(f.Name, ".")
+	for {
+		en := fe.findByName(name, false) // locally only
+		if en == nil {                   // ok we want no value
+			break
+		}
+		count++
+		if len(nameParts) > 1 {
+			name = fmt.Sprintf("%s(%d).%s", nameParts[0], count, strings.Join(nameParts[1:], "."))
+		} else {
+			name = fmt.Sprintf("%s(%d)", nameParts[0], count)
+		}
+	}
+
+	uid, _ := strconv.Atoi(fe.fs.osuser.Uid)
+	gid, _ := strconv.Atoi(fe.fs.osuser.Gid)
+
+	// Create Attribute
+	attr := fuseops.InodeAttributes{}
+	attr.Nlink = 1
+	attr.Size = uint64(f.Size)
+	// Temp
+	attr.Uid = uint32(uid)
+	attr.Gid = uint32(gid)
+
+	attr.Crtime, _ = time.Parse(time.RFC3339, f.CreatedTime)
+	attr.Mtime, _ = time.Parse(time.RFC3339, f.ModifiedTime)
+	attr.Mode = os.FileMode(0644) // default
+	if f.MimeType == "application/vnd.google-apps.folder" {
+		attr.Mode = os.FileMode(0755) | os.ModeDir
+	}
+	// Create an entry
+	entry := &FileEntry{
+		fs:    fe.fs,
+		GFile: f,
+		Name:  name,                    // we dont need name
+		Inode: fe.fs.findUnusedInode(), // Lock somehow
+		Attr:  attr,
+		//fileMap: map[string]*FileEntry{},
+		fileList: []*FileEntry{},
+	}
+
+	fe.fileList = append(fe.fileList, entry)
+	//fe.fileMap[f.Name] = entry
+
+	return entry
+}
+func (fe *FileEntry) findByInode(inode fuseops.InodeID, recurse bool) *FileEntry {
+	if inode == fe.Inode {
+		return fe // return self
+	}
+	// Recurse??
+	for _, e := range fe.fileList {
+		if e.Inode == inode {
+			return e
+		}
+		if recurse {
+			re := e.findByInode(inode, recurse)
+			if re != nil {
+				return re
+			}
+		}
+	}
+	// For each child we findByInode
+	return nil
+}
+
+func (fe *FileEntry) findByName(name string, recurse bool) *FileEntry {
+	// Recurse??
+	for _, e := range fe.fileList {
+		if e.Name == name {
+			return e
+		}
+		if recurse {
+			re := e.findByName(name, recurse)
+			if re != nil {
+				return re
+			}
+		}
+	}
+	// For each child we findByInode
+	return nil
+}
+
+func (fe *FileEntry) findByGID(gdriveID string, recurse bool) *FileEntry {
+	// Recurse??
+	for _, e := range fe.fileList {
+		if e.GFile.Id == gdriveID {
+			return e
+		}
+		if recurse {
+			re := e.findByGID(gdriveID, recurse)
+			if re != nil {
+				return re
+			}
+		}
+	}
+	// For each child we findByInode
+	return nil
+}

+ 617 - 0
src/gdrivemount/gdrive-fuse.go

@@ -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
+}

+ 153 - 0
src/gdrivemount/gdrive-fuse.go.old

@@ -0,0 +1,153 @@
+package gdrivemount
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+
+	"github.com/hanwen/go-fuse/fuse"
+	"github.com/hanwen/go-fuse/fuse/nodefs"
+	"github.com/hanwen/go-fuse/fuse/pathfs"
+	drive "google.golang.org/api/drive/v3"
+)
+
+// GDriveFS google drive fuse FS
+type GDriveFS struct {
+	pathfs.FileSystem
+	srv *drive.Service
+
+	// Cache files somehow??
+	cacheFiles *drive.FileList
+	fileMap    map[string]*drive.File
+}
+
+func (fs *GDriveFS) refresh() {
+	r, err := fs.srv.Files.List().Do()
+	if err != nil {
+		log.Println("Unable to retrieve files")
+	}
+
+	for _, v := range r.Files {
+		name := v.Name
+		count := 1
+		for {
+			_, ok := fs.fileMap[name]
+			if ok { // already exists
+				count++
+				name = fmt.Sprintf("(%d) %s", count, v.Name)
+			} else {
+				fs.fileMap[name] = v
+				break
+			}
+		}
+	}
+	log.Println("Refresh done")
+}
+
+// NewGDriveFS fuse driver for google drive
+func NewGDriveFS() *GDriveFS {
+	srv := GetDrive()
+	fs := GDriveFS{FileSystem: pathfs.NewDefaultFileSystem(), srv: srv}
+	fs.fileMap = map[string]*drive.File{}
+	fs.FileSystem.SetDebug(true)
+
+	fs.refresh()
+
+	return &fs
+}
+
+func (fs *GDriveFS) GetXAttr(name string, attribute string, context *fuse.Context) ([]byte, fuse.Status) {
+	return []byte(""), fuse.ENOSYS
+}
+func (fs *GDriveFS) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
+	// root
+	if name == "" {
+		return &fuse.Attr{Mode: fuse.S_IFDIR | 0755}, fuse.OK
+	}
+
+	f, ok := fs.fileMap[name]
+	// file not found
+	if !ok { // file not found
+		log.Println("File not found:", name)
+		return nil, fuse.ENOENT
+	}
+	// folder
+	if f.MimeType == "application/vnd.google-apps.folder" {
+		return &fuse.Attr{Mode: fuse.S_IFDIR | 0755}, fuse.OK
+	}
+
+	// Regular file
+	return &fuse.Attr{Mode: fuse.S_IFREG | 0644}, fuse.OK
+
+	//return nil, fuse.ENOENT
+}
+
+func (fs *GDriveFS) OpenDir(name string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) {
+	if name == "" { // root
+		res := []fuse.DirEntry{}
+
+		/*r, err := fs.srv.Files.List().Do()
+		if err != nil {
+			log.Println("Unable to retrieve drive Client")
+		}*/
+
+		for k, v := range fs.fileMap {
+			var mode uint32
+			mode = fuse.S_IFREG
+			if v.MimeType == "application/vnd.google-apps.folder" {
+				mode = fuse.S_IFDIR
+			}
+			res = append(res, fuse.DirEntry{Name: k, Mode: mode})
+		}
+		return res, fuse.OK
+	}
+	log.Println("No ent for:", name)
+	return nil, fuse.ENOENT
+
+}
+func (fs *GDriveFS) Open(name string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status) {
+
+	log.Println("Opening file:", name, " with flags:", flags)
+	f, ok := fs.fileMap[name]
+	if !ok {
+		return nil, fuse.ENOENT
+	}
+
+	var res *http.Response
+	var err error
+
+	switch f.MimeType {
+	case "application/vnd.google-apps.document":
+		res, err = fs.srv.Files.Export(f.Id, "text/plain").Download()
+	case "application/vnd.google-apps.spreadsheet":
+		log.Println("Exporting as: text/csv")
+		res, err = fs.srv.Files.Export(f.Id, "text/csv").Download()
+	default:
+		res, err = fs.srv.Files.Get(f.Id).Download()
+	}
+
+	if err != nil {
+		log.Println("MimeType:", f.MimeType)
+		log.Println("Error from GDrive API", err)
+		return nil, fuse.EPERM
+	}
+	defer res.Body.Close()
+
+	localFile, err := ioutil.TempFile(os.TempDir(), "gdfs")
+	if err != nil {
+		log.Println("Error creating temp file")
+		return nil, fuse.ENOSYS
+	}
+	io.Copy(localFile, res.Body)
+
+	localFile.Seek(0, io.SeekStart)
+
+	//localFile.Close()
+	//data, err := ioutil.ReadFile(localFile.Name())
+	// return nodefs.NewDataFile(data), fuse.OK
+	return nodefs.NewLoopbackFile(localFile), fuse.OK
+
+}

+ 135 - 0
src/gdrivemount/gdrive.go

@@ -0,0 +1,135 @@
+package gdrivemount
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"os/user"
+	"path/filepath"
+
+	drive "google.golang.org/api/drive/v3"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+)
+
+func getClient(ctx context.Context, config *oauth2.Config) *http.Client {
+	cacheFile, err := tokenCacheFile()
+	if err != nil {
+		log.Fatalf("Unable to get path to cached credential file. %v", err)
+	}
+
+	tok, err := tokenFromFile(cacheFile)
+	if err != nil {
+		tok = getTokenFromWeb(config)
+		saveToken(cacheFile, tok)
+	}
+	return config.Client(ctx, tok)
+
+}
+
+func tokenCacheFile() (string, error) {
+	tokenCacheDir, err := getConfigPath()
+	if err != nil {
+		return "", err
+	}
+
+	os.MkdirAll(tokenCacheDir, 0700)
+	return filepath.Join(tokenCacheDir, url.QueryEscape("auth.json")), err
+
+}
+
+func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
+	authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
+
+	fmt.Printf("Go to the following link in your browser: %v\ntype the authorization code: ", authURL)
+	var code string
+	if _, err := fmt.Scan(&code); err != nil {
+		log.Fatalf("Unable to read authorization code %v", err)
+	}
+
+	tok, err := config.Exchange(oauth2.NoContext, code)
+	if err != nil {
+		log.Fatalf("Unable to retrieve token from web: %v", err)
+	}
+
+	return tok
+}
+
+func tokenFromFile(file string) (*oauth2.Token, error) {
+	f, err := os.Open(file)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	t := &oauth2.Token{}
+	err = json.NewDecoder(f).Decode(t)
+	return t, err
+}
+
+func saveToken(file string, token *oauth2.Token) {
+	log.Printf("Saving credential file to: %s\n", file)
+	f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		log.Fatalf("Unable to cache oauth token: %v\n", err)
+	}
+	defer f.Close()
+
+	json.NewEncoder(f).Encode(token)
+}
+
+func getConfigPath() (string, error) {
+	usr, err := user.Current()
+	if err != nil {
+		return "", err
+	}
+	configDir := filepath.Join(usr.HomeDir, ".gdrivemount")
+
+	return configDir, nil
+}
+
+func GetDrive() *drive.Service {
+
+	configPath, err := getConfigPath()
+	if err != nil {
+		log.Fatal("Unable to fetch config path")
+	}
+	ctx := context.Background()
+
+	b, err := ioutil.ReadFile(filepath.Join(configPath, "client_secret.json"))
+	if err != nil {
+		log.Fatalf("Unable to read client secret file: %v", err)
+	}
+
+	config, err := google.ConfigFromJSON(b, drive.DriveScope)
+	if err != nil {
+		log.Fatalf("Unable to parse client secret file: %v", err)
+	}
+
+	client := getClient(ctx, config)
+	srv, err := drive.New(client)
+	if err != nil {
+		log.Fatalf("Unable to retrieve drive Client: %v", err)
+	}
+
+	return srv
+
+}
+
+/*func main() {
+	srv := GetDrive()
+	r, err := srv.Files.List().PageSize(10).Fields("nextPageToken, files(id, name)").Do()
+	if err != nil {
+		log.Fatalf("Unable to retrieve files: %v", err)
+	}
+
+	for _, v := range r.Files {
+		log.Printf("%s (%s) %d\n", v.Name, v.Id, v.Size)
+	}
+
+}*/

+ 116 - 0
src/gdrivemount/test/drive/main.go

@@ -0,0 +1,116 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+
+	drive "google.golang.org/api/drive/v3"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+)
+
+func getClient(ctx context.Context, config *oauth2.Config) *http.Client {
+	cacheFile, err := tokenCacheFile()
+	if err != nil {
+		log.Fatalf("Unable to get path to cached credential file. %v", err)
+	}
+
+	tok, err := tokenFromFile(cacheFile)
+	if err != nil {
+		tok = getTokenFromWeb(config)
+		saveToken(cacheFile, tok)
+	}
+	return config.Client(ctx, tok)
+
+}
+
+func tokenCacheFile() (string, error) {
+
+	return "tokendata.json", nil
+
+	/*usr, err := user.Current()
+	if err != nil {
+		return "", err
+	}
+
+	tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials")
+	os.MkdirAll(tokenCacheDir, 0700)
+	return filepath.Join(tokenCacheDir, url.QueryEscape("drive-go-quickstart.json")), err*/
+
+}
+
+func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
+	authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
+
+	fmt.Printf("Go to the following link in your browser then type the authorization code: \n%v\n", authURL)
+	var code string
+	if _, err := fmt.Scan(&code); err != nil {
+		log.Fatalf("Unable to read authorization code %v", err)
+	}
+
+	tok, err := config.Exchange(oauth2.NoContext, code)
+	if err != nil {
+		log.Fatalf("Unable to retrieve token from web: %v", err)
+	}
+
+	return tok
+}
+
+func tokenFromFile(file string) (*oauth2.Token, error) {
+	f, err := os.Open(file)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	t := &oauth2.Token{}
+	err = json.NewDecoder(f).Decode(t)
+	return t, err
+}
+
+func saveToken(file string, token *oauth2.Token) {
+	log.Printf("Saving credential file to: %s\n", file)
+	f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+	if err != nil {
+		log.Fatalf("Unable to cache oauth token: %v\n", err)
+	}
+	defer f.Close()
+
+	json.NewEncoder(f).Encode(token)
+}
+
+func main() {
+	ctx := context.Background()
+	b, err := ioutil.ReadFile("client_secret.json")
+	if err != nil {
+		log.Fatalf("Unable to read client secret file: %v", err)
+	}
+
+	config, err := google.ConfigFromJSON(b, drive.DriveMetadataReadonlyScope)
+	if err != nil {
+		log.Fatalf("Unable to parse client secret file: %v", err)
+	}
+
+	client := getClient(ctx, config)
+
+	srv, err := drive.New(client)
+	if err != nil {
+		log.Fatalf("Unable to retrieve drive Client: %v", err)
+	}
+
+	r, err := srv.Files.List().PageSize(10).Fields("nextPageToken, files(id, name)").Do()
+	if err != nil {
+		log.Fatalf("Unable to retrieve files: %v", err)
+	}
+
+	for _, v := range r.Files {
+		log.Printf("%s (%s) %d\n", v.Name, v.Id, v.Size)
+	}
+
+}

+ 65 - 0
src/gdrivemount/test/fuse/main.go

@@ -0,0 +1,65 @@
+package main
+
+import (
+	"flag"
+	"log"
+
+	"github.com/hanwen/go-fuse/fuse"
+	"github.com/hanwen/go-fuse/fuse/nodefs"
+	"github.com/hanwen/go-fuse/fuse/pathfs"
+)
+
+type HelloFs struct {
+	pathfs.FileSystem
+}
+
+func (m *HelloFs) GetAttr(name string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
+	switch name {
+	case "Hi.txt":
+		return &fuse.Attr{
+			Mode: fuse.S_IFREG | 0644, Size: 10}, fuse.OK
+	case "":
+		return &fuse.Attr{Mode: fuse.S_IFDIR | 0755}, fuse.OK
+	}
+	return nil, fuse.ENOENT
+}
+
+func (m *HelloFs) OpenDir(name string, context *fuse.Context) (c []fuse.DirEntry, code fuse.Status) {
+	if name == "" {
+		c = []fuse.DirEntry{
+			{Name: "Hi.txt", Mode: fuse.S_IFREG},
+		}
+
+		return c, fuse.OK
+	}
+	return nil, fuse.ENOENT
+}
+
+func (m *HelloFs) Open(name string, flags uint32, context *fuse.Context) (file nodefs.File, code fuse.Status) {
+	if name != "Hi.txt" {
+		return nil, fuse.ENOENT
+	}
+
+	if flags&fuse.O_ANYWRITE != 0 {
+		return nil, fuse.EPERM
+	}
+
+	return nodefs.NewDataFile([]byte("ok")), fuse.OK
+
+}
+
+func main() {
+
+	flag.Parse()
+	if len(flag.Args()) < 1 {
+		log.Fatal("Usage:\n gdrivemount MOUNTPOINT")
+	}
+	nfs := pathfs.NewPathNodeFs(&HelloFs{FileSystem: pathfs.NewDefaultFileSystem()}, nil)
+
+	server, _, err := nodefs.MountRoot(flag.Arg(0), nfs.Root(), nil)
+	if err != nil {
+		log.Fatalf("Mount fail: %v\n", err)
+	}
+
+	server.Serve()
+}