Преглед изворни кода

Changed alot on structure

moved gdrivefs to basefs
created gdrivefs
cleaned up some entries
remove any services on FileEntry (it should only have getters setters if
requred)
luis пре 7 година
родитељ
комит
93de348aea

+ 8 - 0
README.md

@@ -100,4 +100,12 @@ HUP    | Perform a GC and shows memory usage <small>Works when its not running i
 #### TODO & IDEAS:
 * Consider using github.com/codegangsta/cli
 * Create test suit to implement new packages
+* GDrive: long term caching, maintain most used files locally until flush/change
+* File_container can be common for all FS?
+* Define what should be common per FS and create an interface for implementations
 
+
+#### Plan:
+
+Create a common structure for driver
+// Driver needs populate list somehow

+ 4 - 4
internal/core/core.go

@@ -27,7 +27,7 @@ type Core struct {
 	Config  Config
 	Drivers map[string]DriverFactory
 
-	CurrentFS Driver
+	CurrentFS DriverFS
 }
 
 // New create a New cloudmount core
@@ -107,9 +107,9 @@ func (c *Core) Mount() {
 		for sig := range sigs {
 			log.Println("Signal:", sig)
 			switch sig {
-			case syscall.SIGUSR1:
-				log.Println("Manually Refresh drive")
-				go c.CurrentFS.Refresh()
+			//case syscall.SIGUSR1:
+			//log.Println("Manually Refresh drive")
+			//go c.CurrentFS.Refresh()
 			case syscall.SIGHUP:
 				log.Println("GC")
 				mem := runtime.MemStats{}

+ 3 - 3
internal/core/driver.go

@@ -3,11 +3,11 @@ package core
 import "github.com/jacobsa/fuse/fuseutil"
 
 // Base Driver
-type Driver interface {
+type DriverFS interface {
 	fuseutil.FileSystem
 	//Init()
 	Start()
-	Refresh()
+	//Refresh()
 }
 
-type DriverFactory func(*Core) Driver
+type DriverFactory func(*Core) DriverFS

+ 689 - 0
internal/fs/bakfs/basefs.go

@@ -0,0 +1,689 @@
+package basefs
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strings"
+	"sync"
+	"syscall"
+	"time"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+	"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("commonfs")
+)
+
+// BaseFS struct
+type BaseFS struct {
+	fuseutil.NotImplementedFileSystem // Defaults
+
+	Config *core.Config //core   *core.Core // Core Config instead?
+	//serviceConfig                     *Config
+	Client *drive.Service
+
+	fileHandles map[fuseops.HandleID]*Handle
+	fileEntries map[fuseops.InodeID]*FileEntry
+
+	//nextRefresh time.Time
+	handleMU *sync.Mutex
+	inodeMU  *sync.Mutex
+	//fileMap map[string]
+	// Map IDS with FileEntries
+}
+
+func New(core *core.Core) *BaseFS {
+
+	fs := &BaseFS{
+		Config: &core.Config,
+		//serviceConfig: &Config{}, // This is on service Driver
+		fileHandles: map[fuseops.HandleID]*Handle{},
+		handleMU:    &sync.Mutex{},
+	}
+	// Temporary entry
+	entry := fs.fileEntry("Loading...", nil, 9999)
+	entry.Attr.Mode = os.FileMode(0)
+
+	return fs
+}
+
+// Async
+func (fs *BaseFS) Start() {
+	go func() {
+		//fs.Refresh() // First load
+
+		// Change reader loop
+		/*startPageTokenRes, err := fs.root.client.Changes.GetStartPageToken().Do()
+		if err != nil {
+			log.Println("GDrive err", err)
+		}
+		savedStartPageToken := startPageTokenRes.StartPageToken
+		for {
+			pageToken := savedStartPageToken
+			for pageToken != "" {
+				changesRes, err := fs.root.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
+				if err != nil {
+					log.Println("Err fetching changes", err)
+					break
+				}
+				//log.Println("Changes:", len(changesRes.Changes))
+				for _, c := range changesRes.Changes {
+					entry := fs.root.FindByGID(c.FileId)
+					if c.Removed {
+						if entry == nil {
+							continue
+						} else {
+							fs.root.RemoveEntry(entry)
+						}
+						continue
+					}
+
+					if entry != nil {
+						entry.SetGFile(c.File)
+					} else {
+						//Create new one
+						fs.root.FileEntry(c.File) // Creating new one
+					}
+				}
+				if changesRes.NewStartPageToken != "" {
+					savedStartPageToken = changesRes.NewStartPageToken
+				}
+				pageToken = changesRes.NextPageToken
+			}
+
+			time.Sleep(fs.config.RefreshTime)
+		}*/
+	}()
+}
+
+////////////////////////////////////////////////////////
+// TOOLS & HELPERS
+////////////////////////////////////////////////////////
+
+// COMMON
+func (fs *BaseFS) createHandle() *Handle {
+	// Lock here instead
+	fs.handleMU.Lock()
+	defer fs.handleMU.Unlock()
+
+	var handleID fuseops.HandleID
+
+	for handleID = 1; handleID < 99999; handleID++ {
+		_, ok := fs.fileHandles[handleID]
+		if !ok {
+			break
+		}
+	}
+
+	handle := &Handle{ID: handleID}
+	fs.fileHandles[handleID] = handle
+
+	return handle
+}
+
+const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
+const gdFields = googleapi.Field("files(" + fileFields + ")")
+
+// FULL Refresh service files
+
+///////////////////////////////
+// Fuse operations
+////////////
+
+// OpenDir return nil error allows open dir
+// COMMON for drivers
+func (fs *BaseFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
+
+	entry := fs.findByInode(op.Inode)
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	handle := fs.createHandle()
+	handle.entry = entry
+	op.Handle = handle.ID
+
+	return // No error allow, dir open
+}
+
+// ReadDir lists files into readdirop
+// Common for drivers
+func (fs *BaseFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
+	fh, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		log.Fatal("Handle does not exists")
+	}
+
+	if op.Offset == 0 { // Rebuild/rewind dir list
+		fh.entries = []fuseutil.Dirent{}
+
+		children := fs.listByParent(fh.entry)
+
+		for i, v := range children {
+			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)
+		}
+	}
+
+	index := int(op.Offset)
+	if index > len(fh.entries) {
+		return fuse.EINVAL
+	}
+	if index > 0 {
+		index++
+	}
+	for i := index; i < len(fh.entries); i++ {
+		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
+		if n == 0 {
+			break
+		}
+		op.BytesRead += n
+	}
+	return
+}
+
+// SPECIFIC code
+func (fs *BaseFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
+
+	// Hack to truncate file?
+
+	if op.Size != nil {
+		entry := fs.Root.FindByInode(op.Inode)
+
+		if *op.Size != 0 { // We only allow truncate to 0
+			return fuse.ENOSYS
+		}
+		err = entry.Truncate()
+	}
+
+	return
+}
+
+//GetInodeAttributes return attributes
+// COMMON
+func (fs *BaseFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
+
+	f := fs.findByInode(op.Inode)
+	if f == nil {
+		return fuse.ENOENT
+	}
+	op.Attributes = f.Attr
+	op.AttributesExpiration = time.Now().Add(time.Minute)
+
+	return
+}
+
+// ReleaseDirHandle deletes file handle entry
+// COMMON
+func (fs *BaseFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
+	delete(fs.fileHandles, op.Handle)
+	return
+}
+
+// LookUpInode based on Parent and Name we return a self cached inode
+// Cloud be COMMON but has specific ID
+func (fs *BaseFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
+
+	parentFile := fs.findByInode(op.Parent) // true means transverse all
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+
+	entry := fs.lookupByParent(parentFile, op.Name)
+
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	// Transverse only local
+
+	now := time.Now()
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: now.Add(time.Second),
+		EntryExpiration:      now.Add(time.Second),
+	}
+	return
+}
+
+// ForgetInode allows to forgetInode
+// COMMON
+func (fs *BaseFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
+	return
+}
+
+// GetXAttr special attributes
+// COMMON
+func (fs *BaseFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
+	return
+}
+
+//////////////////////////////////////////////////////////////////////////
+// File OPS
+//////////////////////////////////////////////////////////////////////////
+
+// OpenFile creates a temporary handle to be handled on read or write
+// COMMON
+func (fs *BaseFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
+	f := fs.findByInode(op.Inode) // might not exists
+
+	// Generate new handle
+	handle := fs.createHandle()
+	handle.entry = f
+
+	op.Handle = handle.ID
+	op.UseDirectIO = true
+
+	return
+}
+
+// COMMON but specific in cache
+func (fs *BaseFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
+	handle := fs.fileHandles[op.Handle]
+
+	localFile := handle.entry.Cache()
+	op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
+	if err == io.EOF { // fuse does not expect a EOF
+		err = nil
+	}
+
+	return
+}
+
+// CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
+// Cloud SPECIFIC
+func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
+
+	parentFile := fs.findByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	// Only write on child folders
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	existsFile := fs.lookupByParent(parentFile, op.Name)
+	//existsFile := parentFile.FindByName(op.Name, false)
+	if existsFile != nil {
+		return fuse.EEXIST
+	}
+
+	// Parent entry/Name
+	entry, err := fs.createFile(parentFile, op.Name, false)
+	if err != nil {
+		return err
+	}
+	// Associate a temp file to a new handle
+	// Local copy
+	// Lock
+	handle := fs.createHandle()
+	handle.entry = entry
+	handle.uploadOnDone = true
+	//
+	op.Handle = handle.ID
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Minute),
+	}
+	op.Mode = entry.Attr.Mode
+
+	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
+// CLOUD SPECIFIC
+func (fs *BaseFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+
+	localFile := handle.entry.Cache()
+	if localFile == nil {
+		return fuse.EINVAL
+	}
+
+	_, err = localFile.WriteAt(op.Data, op.Offset)
+	if err != nil {
+		err = fuse.EIO
+		return
+	}
+	handle.uploadOnDone = true
+
+	return
+}
+
+// FlushFile just returns no error, maybe upload should be handled here
+// COMMON
+func (fs *BaseFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+	if handle.entry.tempFile == nil {
+		return
+	}
+	if handle.uploadOnDone { // or if content changed basically
+		err = handle.entry.Sync()
+		if err != nil {
+			return fuse.EINVAL
+		}
+	}
+	return
+}
+
+// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
+// COMMON
+func (fs *BaseFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
+	handle := fs.fileHandles[op.Handle]
+
+	handle.entry.ClearCache()
+
+	delete(fs.fileHandles, op.Handle)
+
+	return
+}
+
+// Unlink remove file and remove from local cache entry
+// SPECIFIC
+func (fs *BaseFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
+	parentEntry := fs.findByInode(op.Parent)
+	if parentEntry == nil {
+		return fuse.ENOENT
+	}
+	if parentEntry.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	fileEntry := fs.lookupByParent(parentEntry, op.Name)
+	//fileEntry := parentEntry.FindByName(op.Name, false)
+	if fileEntry == nil {
+		return fuse.ENOATTR
+	}
+	return fs.deleteFile(fileEntry)
+}
+
+// MkDir creates a directory on a parent dir
+func (fs *BaseFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
+
+	parentFile := fs.findByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	entry, err := fs.createFile(parentFile, op.Name, true)
+	if err != nil {
+		return err
+	}
+	//entry = parentFile.AppendGFile(fi, entry.Inode)
+	//if entry == nil {
+	//		return fuse.EINVAL
+	//	}
+
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Microsecond),
+	}
+
+	return
+}
+
+// RmDir fuse implementation
+func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
+
+	parentFile := fs.findByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	theFile := fs.lookupByParent(parentFile, op.Name)
+	//theFile := parentFile.FindByName(op.Name, false)
+
+	err = fs.deleteFile(theFile)
+	//err = fs.Client.Files.Delete(theFile.GFile.Id).Do()
+	if err != nil {
+		return fuse.ENOTEMPTY
+	}
+
+	//parentFile.RemoveChild(theFile)
+
+	// Remove from entry somehow
+
+	return
+}
+
+// Rename fuse implementation
+func (fs *BaseFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
+	oldParentFile := fs.findByInode(op.OldParent)
+	if oldParentFile == nil {
+		return fuse.ENOENT
+	}
+	newParentFile := fs.findByInode(op.NewParent)
+	if newParentFile == nil {
+		return fuse.ENOENT
+	}
+
+	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	//oldFile := oldParentFile.FindByName(op.OldName, false)
+	oldEntry := fs.lookupByParent(oldParentFile, op.OldName)
+
+	// Although GDrive allows duplicate names, there is some issue with inode caching
+	// So we prevent a rename to a file with same name
+	//existsFile := newParentFile.FindByName(op.NewName, false)
+	existsEntry := fs.lookupByGID(newParentFile.GID, op.NewName)
+	if existsEntry != nil {
+		return fuse.EEXIST
+	}
+
+	// Rename somehow
+	ngFile := &drive.File{
+		Name: op.NewName,
+	}
+
+	updateCall := fs.Client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
+	if oldParentFile != newParentFile {
+		updateCall.RemoveParents(oldParentFile.GID)
+		updateCall.AddParents(newParentFile.GID)
+	}
+	updatedFile, err := updateCall.Do()
+
+	oldEntry.SetGFile(updatedFile)
+
+	//oldParentFile.RemoveChild(oldFile)
+	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
+
+	return
+}
+
+func (fs *BaseFS) findByInode(inode fuseops.InodeID) *FileEntry {
+	return fs.fileEntries[inode]
+}
+
+// GID specific functions
+func (fs *BaseFS) findByGID(gid string) *FileEntry {
+	for _, v := range fs.fileEntries {
+		if v.GFile != nil && v.GFile.Id == gid {
+			return v
+		}
+	}
+	return nil
+}
+func (fs *BaseFS) lookupByGID(parentGID string, name string) *FileEntry {
+	for _, entry := range fs.fileEntries {
+		if entry.HasParentGID(parentGID) && entry.Name == name {
+			return entry
+		}
+	}
+	return nil
+}
+
+func (fs *BaseFS) listByParent(parent *FileEntry) []*FileEntry {
+	ret := []*FileEntry{}
+	for _, entry := range fs.fileEntries {
+		if entry.HasParentGID(parent.GID) {
+			ret = append(ret, entry)
+		}
+	}
+	return ret
+}
+func (fs *BaseFS) lookupByParent(parent *FileEntry, name string) *FileEntry {
+	for _, entry := range fs.fileEntries {
+		if entry.HasParentGID(parent.GID) && entry.Name == name {
+			return entry
+		}
+	}
+	return nil
+
+}
+
+func (fs *BaseFS) createFile(parentFile *FileEntry, name string, isDir bool) (*FileEntry, error) {
+
+	newGFile := &drive.File{
+		Parents: []string{parentFile.GFile.Id},
+		Name:    name,
+	}
+	if isDir {
+		newGFile.MimeType = "application/vnd.google-apps.folder"
+	}
+	// Could be transformed to CreateFile in continer
+	// InDriver
+	createdGFile, err := fs.Client.Files.Create(newGFile).Fields(fileFields).Do()
+	if err != nil {
+		return nil, fuse.EINVAL
+	}
+	entry := fs.fileEntry(createdGFile.Name, createdGFile) // New Entry added // Or Return same?
+
+	return entry, nil
+}
+
+func (fs *BaseFS) deleteFile(entry *FileEntry) error {
+	err := fs.Client.Files.Delete(entry.GFile.Id).Do()
+	if err != nil {
+		return fuse.EIO
+	}
+
+	fs.removeEntry(entry)
+	return nil
+}
+
+//////////////
+
+//Return or create inode // Pass name maybe?
+func (fs *BaseFS) fileEntry(aname string, gfile *drive.File, inodeOps ...fuseops.InodeID) *FileEntry {
+
+	fs.inodeMU.Lock()
+	defer fs.inodeMU.Unlock()
+
+	var inode fuseops.InodeID
+	if len(inodeOps) > 0 {
+		inode = inodeOps[0]
+		if fe, ok := fs.fileEntries[inode]; ok {
+			return fe
+		}
+	} else { // generate new inode
+		// Max Inode Number
+		for inode = 2; inode < 99999; inode++ {
+			_, ok := fs.fileEntries[inode]
+			if !ok {
+				break
+			}
+		}
+	}
+
+	name := ""
+	if gfile != nil {
+		name = aname
+		count := 1
+		nameParts := strings.Split(name, ".")
+		for {
+			// We find if we have a GFile in same parent with same name
+			var entry *FileEntry
+			for _, p := range gfile.Parents {
+				entry = fs.lookupByGID(p, name)
+				if entry != nil {
+					break
+				}
+			}
+			if entry == nil { // Not found return
+				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)
+			}
+			log.Printf("Conflicting name generated new '%s' as '%s'", gfile.Name, name)
+		}
+	}
+
+	fe := &FileEntry{
+		GFile: gfile,
+		Inode: inode,
+		fs:    fs,
+		Name:  name,
+		//children:  []*FileEntry{},
+		Attr: fuseops.InodeAttributes{
+			Uid: fs.Config.UID,
+			Gid: fs.Config.GID,
+		},
+	}
+	fe.SetGFile(gfile)
+
+	fs.fileEntries[inode] = fe
+
+	return fe
+}
+
+/*func (fs *BaseFS) addEntry(entry *FileEntry) {
+	fc.fileEntries[entry.Inode] = entry
+}*/
+
+// RemoveEntry remove file entry
+func (fs *BaseFS) removeEntry(entry *FileEntry) {
+	var inode fuseops.InodeID
+	for k, e := range fs.fileEntries {
+		if e == entry {
+			inode = k
+		}
+	}
+	delete(fs.fileEntries, inode)
+}

+ 4 - 0
internal/fs/bakfs/driver.go

@@ -0,0 +1,4 @@
+package basefs
+
+type Driver interface {
+}

+ 27 - 44
internal/fs/gdrivefs/file_entry.go

@@ -1,4 +1,4 @@
-package gdrivefs
+package basefs
 
 import (
 	"io"
@@ -7,6 +7,7 @@ import (
 	"os"
 	"time"
 
+	"github.com/jacobsa/fuse"
 	"github.com/jacobsa/fuse/fuseops"
 	drive "google.golang.org/api/drive/v3"
 )
@@ -14,10 +15,9 @@ import (
 //FileEntry entry to handle files
 type FileEntry struct {
 	//parent *FileEntry
-	container *FileContainer
-	//fs    *GDriveFS
+	fs    *BaseFS
 	GID   string      // google driveID
-	GFile *drive.File // GDrive file
+	GFile *drive.File // GDrive file // Interface maybe?
 	Name  string      // local name
 	// fuseops
 	Inode fuseops.InodeID
@@ -29,6 +29,7 @@ type FileEntry struct {
 	//children []*FileEntry // children
 }
 
+// Why?
 func (fe *FileEntry) HasParentGID(gid string) bool {
 
 	// Exceptional case
@@ -55,50 +56,20 @@ func (fe *FileEntry) HasParentGID(gid string) bool {
 	return false
 }
 
-/*func (fe *FileEntry) AddChild(child *FileEntry) {
-	//child.parent = fe // is this needed at all?
-	// Solve name here?
-
-	fe.children = append(fe.children, child)
-}*/
-
-/*func (fe *FileEntry) RemoveChild(child *FileEntry) {
-	toremove := -1
-	for i, v := range fe.children {
-		if v == child {
-			toremove = i
-			break
-		}
-	}
-	if toremove == -1 {
-		return
-	}
-	fe.children = append(fe.children[:toremove], fe.children[toremove+1:]...)
-}*/
-
-// useful for debug to count children
-/*func (fe *FileEntry) Count() int {
-	count := 0
-
-	for _, c := range fe.children {
-		count += c.Count()
-	}
-	return count + len(fe.children)
-}*/
-
 // SetGFile update attributes and set drive.File
-func (fe *FileEntry) SetGFile(gfile *drive.File) {
+func (fe *FileEntry) SetGFile(gfile *drive.File) { // Should remove from here maybe?
 	if gfile == nil {
 		fe.GFile = nil
 	} else {
 		fe.GFile = gfile
 	}
 
+	// GetAttribute from GFile somehow
 	// Create Attribute
 	attr := fuseops.InodeAttributes{}
 	attr.Nlink = 1
-	attr.Uid = fe.container.uid
-	attr.Gid = fe.container.gid
+	attr.Uid = fe.fs.Config.UID
+	attr.Gid = fe.fs.Config.GID
 
 	attr.Mode = os.FileMode(0644) // default
 	if gfile != nil {
@@ -125,7 +96,7 @@ func (fe *FileEntry) Sync() (err error) {
 	fe.tempFile.Seek(0, io.SeekStart)
 
 	ngFile := &drive.File{}
-	up := fe.container.fs.client.Files.Update(fe.GFile.Id, ngFile)
+	up := fe.fs.Client.Files.Update(fe.GFile.Id, ngFile)
 	upFile, err := up.Media(fe.tempFile).Fields(fileFields).Do()
 
 	fe.SetGFile(upFile) // update local GFile entry
@@ -152,19 +123,18 @@ func (fe *FileEntry) Cache() *os.File {
 	var res *http.Response
 	var err error
 	// Export GDocs (Special google doc documents needs to be exported make a config somewhere for this)
-	switch fe.GFile.MimeType { // Make this somewhat optional
+	switch fe.GFile.MimeType { // Make this somewhat optional special case
 	case "application/vnd.google-apps.document":
 		log.Println("Exporting as: text/markdown")
-		res, err = fe.container.fs.client.Files.Export(fe.GFile.Id, "text/plain").Download()
+		res, err = fe.fs.Client.Files.Export(fe.GFile.Id, "text/plain").Download()
 	case "application/vnd.google-apps.spreadsheet":
 		log.Println("Exporting as: text/csv")
-		res, err = fe.container.fs.client.Files.Export(fe.GFile.Id, "text/csv").Download()
+		res, err = fe.fs.Client.Files.Export(fe.GFile.Id, "text/csv").Download()
 	default:
-		res, err = fe.container.fs.client.Files.Get(fe.GFile.Id).Download()
+		res, err = fe.fs.Client.Files.Get(fe.GFile.Id).Download()
 	}
 
 	if err != nil {
-		log.Println("MimeType:", fe.GFile.MimeType)
 		log.Println("Error from GDrive API", err)
 		return nil
 	}
@@ -183,6 +153,19 @@ func (fe *FileEntry) Cache() *os.File {
 
 }
 
+func (fe *FileEntry) Truncate() (err error) { // DriverTruncate
+	// Delete and create another on truncate 0
+	err = fe.fs.Client.Files.Delete(fe.GFile.Id).Do() // XXX: Careful on this
+	createdFile, err := fe.fs.Client.Files.Create(&drive.File{Parents: fe.GFile.Parents, Name: fe.GFile.Name}).Fields(fileFields).Do()
+	if err != nil {
+		return fuse.EINVAL // ??
+	}
+	fe.SetGFile(createdFile) // Set new file
+
+	return
+}
+
+// IsDir returns true if entry is a directory:w
 func (fe *FileEntry) IsDir() bool {
 	return fe.Attr.Mode&os.ModeDir == os.ModeDir
 }

+ 14 - 0
internal/fs/bakfs/handle.go

@@ -0,0 +1,14 @@
+package basefs
+
+import (
+	"github.com/jacobsa/fuse/fuseops"
+	"github.com/jacobsa/fuse/fuseutil"
+)
+
+type Handle struct {
+	ID           fuseops.HandleID
+	entry        *FileEntry
+	uploadOnDone bool
+	// Handling for dir
+	entries []fuseutil.Dirent
+}

+ 491 - 0
internal/fs/basefs/basefs.go

@@ -0,0 +1,491 @@
+// basefs implements a google drive fuse driver
+package basefs
+
+import (
+	"io"
+	"os"
+	"sync"
+	"syscall"
+	"time"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+	"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("basefs")
+)
+
+type Handle struct {
+	ID           fuseops.HandleID
+	entry        *FileEntry
+	uploadOnDone bool
+	// Handling for dir
+	entries []fuseutil.Dirent
+}
+
+// BaseFS data
+type BaseFS struct {
+	fuseutil.NotImplementedFileSystem // Defaults
+
+	Config *core.Config //core   *core.Core // Core Config instead?
+	Root   *FileContainer
+
+	fileHandles map[fuseops.HandleID]*Handle
+	handleMU    *sync.Mutex
+	//serviceConfig *Config
+	Client  *drive.Service
+	Service Service
+	//root   *FileEntry // hiearchy reference
+
+	//fileMap map[string]
+	// Map IDS with FileEntries
+}
+
+// New Creates a new BaseFS with config based on core
+func New(core *core.Core) *BaseFS {
+
+	fs := &BaseFS{
+		Config:      &core.Config,
+		fileHandles: map[fuseops.HandleID]*Handle{},
+		handleMU:    &sync.Mutex{},
+	}
+	fs.Root = NewFileContainer(fs)
+	fs.Root.uid = core.Config.UID
+	fs.Root.gid = core.Config.GID
+
+	_, entry := fs.Root.FileEntry(&drive.File{Id: "0", Name: "Loading..."}, 9999)
+	entry.Attr.Mode = os.FileMode(0)
+
+	return fs
+}
+
+////////////////////////////////////////////////////////
+// TOOLS & HELPERS
+////////////////////////////////////////////////////////
+
+// COMMON
+func (fs *BaseFS) createHandle() *Handle {
+	// Lock here instead
+	fs.handleMU.Lock()
+	defer fs.handleMU.Unlock()
+
+	var handleID fuseops.HandleID
+
+	for handleID = 1; handleID < 99999; handleID++ {
+		_, ok := fs.fileHandles[handleID]
+		if !ok {
+			break
+		}
+	}
+
+	handle := &Handle{ID: handleID}
+	fs.fileHandles[handleID] = handle
+
+	return handle
+}
+
+// Client is still here
+const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
+const gdFields = googleapi.Field("files(" + fileFields + ")")
+
+///////////////////////////////
+// Fuse operations
+////////////
+
+// OpenDir return nil error allows open dir
+// COMMON for drivers
+func (fs *BaseFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
+
+	entry := fs.Root.FindByInode(op.Inode)
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	handle := fs.createHandle()
+	handle.entry = entry
+	op.Handle = handle.ID
+
+	return // No error allow, dir open
+}
+
+// ReadDir lists files into readdirop
+// Common for drivers
+func (fs *BaseFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
+	fh, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		log.Fatal("Handle does not exists")
+	}
+
+	if op.Offset == 0 { // Rebuild/rewind dir list
+		fh.entries = []fuseutil.Dirent{}
+		children := fs.Root.ListByParent(fh.entry)
+
+		i := 0
+		for inode, v := range children {
+			fusetype := fuseutil.DT_File
+			if v.IsDir() {
+				fusetype = fuseutil.DT_Directory
+			}
+			dirEnt := fuseutil.Dirent{
+				Inode:  inode,
+				Name:   v.Name,
+				Type:   fusetype,
+				Offset: fuseops.DirOffset(i) + 1,
+			}
+			//	written += fuseutil.WriteDirent(fh.buf[written:], dirEnt)
+			fh.entries = append(fh.entries, dirEnt)
+			i++
+		}
+	}
+
+	index := int(op.Offset)
+	if index > len(fh.entries) {
+		return fuse.EINVAL
+	}
+	if index > 0 {
+		index++
+	}
+	for i := index; i < len(fh.entries); i++ {
+		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
+		if n == 0 {
+			break
+		}
+		op.BytesRead += n
+	}
+	return
+}
+
+// SetInodeAttributes Not sure what attributes gdrive support we just leave this blank for now
+// SPECIFIC code
+func (fs *BaseFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
+
+	// Hack to truncate file?
+
+	if op.Size != nil {
+		entry := fs.Root.FindByInode(op.Inode)
+
+		if *op.Size != 0 { // We only allow truncate to 0
+			return fuse.ENOSYS
+		}
+		err = fs.Root.Truncate(entry)
+	}
+
+	return
+}
+
+//GetInodeAttributes return attributes
+// COMMON
+func (fs *BaseFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
+
+	f := fs.Root.FindByInode(op.Inode)
+	if f == nil {
+		return fuse.ENOENT
+	}
+	op.Attributes = f.Attr
+	op.AttributesExpiration = time.Now().Add(time.Minute)
+
+	return
+}
+
+// ReleaseDirHandle deletes file handle entry
+// COMMON
+func (fs *BaseFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
+	delete(fs.fileHandles, op.Handle)
+	return
+}
+
+// LookUpInode based on Parent and Name we return a self cached inode
+// Cloud be COMMON but has specific ID
+func (fs *BaseFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
+
+	parentFile := fs.Root.FindByInode(op.Parent) // true means transverse all
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+
+	inode, entry := fs.Root.Lookup(parentFile, op.Name)
+
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	// Transverse only local
+
+	now := time.Now()
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                inode,
+		AttributesExpiration: now.Add(time.Second),
+		EntryExpiration:      now.Add(time.Second),
+	}
+	return
+}
+
+// StatFS basically allows StatFS to run
+/*func (fs *BaseFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
+	return
+}*/
+
+// ForgetInode allows to forgetInode
+// COMMON
+func (fs *BaseFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
+	return
+}
+
+// GetXAttr special attributes
+// COMMON
+func (fs *BaseFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
+	return
+}
+
+//////////////////////////////////////////////////////////////////////////
+// File OPS
+//////////////////////////////////////////////////////////////////////////
+
+// OpenFile creates a temporary handle to be handled on read or write
+// COMMON
+func (fs *BaseFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
+	f := fs.Root.FindByInode(op.Inode) // might not exists
+
+	// Generate new handle
+	handle := fs.createHandle()
+	handle.entry = f
+
+	op.Handle = handle.ID
+	op.UseDirectIO = true
+
+	return
+}
+
+// ReadFile  if the first time we download the google drive file into a local temporary file
+// COMMON but specific in cache
+func (fs *BaseFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
+	handle := fs.fileHandles[op.Handle]
+
+	localFile := fs.Root.Cache(handle.entry)
+	op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
+	if err == io.EOF { // fuse does not expect a EOF
+		err = nil
+	}
+
+	return
+}
+
+// CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
+// Cloud SPECIFIC
+func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
+
+	parentFile := fs.Root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	// Only write on child folders
+	if parentFile == fs.Root.fileEntries[fuseops.RootInodeID] {
+		return syscall.EPERM
+	}
+
+	_, existsFile := fs.Root.Lookup(parentFile, op.Name)
+	//existsFile := parentFile.FindByName(op.Name, false)
+	if existsFile != nil {
+		return fuse.EEXIST
+	}
+
+	// Parent entry/Name
+	inode, entry, err := fs.Root.CreateFile(parentFile, op.Name, false)
+	if err != nil {
+		return err
+	}
+	// Associate a temp file to a new handle
+	// Local copy
+	// Lock
+	handle := fs.createHandle()
+	handle.entry = entry
+	handle.uploadOnDone = true
+	//
+	op.Handle = handle.ID
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Minute),
+	}
+	op.Mode = entry.Attr.Mode
+
+	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
+// CLOUD SPECIFIC
+func (fs *BaseFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+
+	localFile := fs.Root.Cache(handle.entry)
+	if localFile == nil {
+		return fuse.EINVAL
+	}
+
+	_, err = localFile.WriteAt(op.Data, op.Offset)
+	if err != nil {
+		err = fuse.EIO
+		return
+	}
+	handle.uploadOnDone = true
+
+	return
+}
+
+// FlushFile just returns no error, maybe upload should be handled here
+// COMMON
+func (fs *BaseFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+	if handle.entry.tempFile == nil {
+		return
+	}
+	if handle.uploadOnDone { // or if content changed basically
+		err = fs.Root.Sync(handle.entry)
+		if err != nil {
+			return fuse.EINVAL
+		}
+	}
+	return
+}
+
+// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
+// COMMON
+func (fs *BaseFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
+	handle := fs.fileHandles[op.Handle]
+
+	fs.Root.ClearCache(handle.entry)
+
+	delete(fs.fileHandles, op.Handle)
+
+	return
+}
+
+// Unlink remove file and remove from local cache entry
+// SPECIFIC
+func (fs *BaseFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
+	if op.Parent == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+	parentEntry := fs.Root.FindByInode(op.Parent)
+	if parentEntry == nil {
+		return fuse.ENOENT
+	}
+
+	_, fileEntry := fs.Root.Lookup(parentEntry, op.Name)
+	//fileEntry := parentEntry.FindByName(op.Name, false)
+	if fileEntry == nil {
+		return fuse.ENOATTR
+	}
+	return fs.Root.DeleteFile(fileEntry)
+}
+
+// MkDir creates a directory on a parent dir
+func (fs *BaseFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
+	if op.Parent == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	parentFile := fs.Root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+
+	inode, entry, err := fs.Root.CreateFile(parentFile, op.Name, true)
+	if err != nil {
+		return err
+	}
+
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Microsecond),
+	}
+
+	return
+}
+
+// RmDir fuse implementation
+func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
+	if op.Parent == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	parentFile := fs.Root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+
+	_, theFile := fs.Root.Lookup(parentFile, op.Name)
+
+	err = fs.Root.DeleteFile(theFile)
+	if err != nil {
+		return fuse.ENOTEMPTY
+	}
+
+	return
+}
+
+// Rename fuse implementation
+func (fs *BaseFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
+	if op.OldParent == fuseops.RootInodeID || op.NewParent == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+	oldParentFile := fs.Root.FindByInode(op.OldParent)
+	if oldParentFile == nil {
+		return fuse.ENOENT
+	}
+	newParentFile := fs.Root.FindByInode(op.NewParent)
+	if newParentFile == nil {
+		return fuse.ENOENT
+	}
+
+	//oldFile := oldParentFile.FindByName(op.OldName, false)
+	_, oldEntry := fs.Root.Lookup(oldParentFile, op.OldName)
+
+	// Although GDrive allows duplicate names, there is some issue with inode caching
+	// So we prevent a rename to a file with same name
+	//existsFile := newParentFile.FindByName(op.NewName, false)
+	_, existsEntry := fs.Root.Lookup(newParentFile, op.NewName)
+	if existsEntry != nil {
+		return fuse.EEXIST
+	}
+
+	ngFile := &drive.File{
+		Name: op.NewName,
+	}
+
+	updateCall := fs.Client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
+	if oldParentFile != newParentFile {
+		updateCall.RemoveParents(oldParentFile.GID)
+		updateCall.AddParents(newParentFile.GID)
+	}
+	updatedFile, err := updateCall.Do()
+
+	oldEntry.SetFile(&GFile{updatedFile}, fs.Config.UID, fs.Config.GID)
+
+	//oldParentFile.RemoveChild(oldFile)
+	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
+
+	return
+
+}

+ 10 - 0
internal/fs/basefs/file.go

@@ -0,0 +1,10 @@
+package basefs
+
+import "github.com/jacobsa/fuse/fuseops"
+
+type File interface {
+	ID() string
+	Name() string
+	Attr() fuseops.InodeAttributes
+	Parents() []string
+}

+ 261 - 0
internal/fs/basefs/file_container.go

@@ -0,0 +1,261 @@
+package basefs
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"sync"
+
+	drive "google.golang.org/api/drive/v3"
+
+	"github.com/jacobsa/fuse"
+	"github.com/jacobsa/fuse/fuseops"
+)
+
+type FileContainer struct {
+	fileEntries map[fuseops.InodeID]*FileEntry
+	///	tree        *FileEntry
+	fs *BaseFS
+	//client *drive.Service // Wrong should be common
+	uid uint32
+	gid uint32
+
+	inodeMU *sync.Mutex
+}
+
+func NewFileContainer(fs *BaseFS) *FileContainer {
+	fc := &FileContainer{
+		fileEntries: map[fuseops.InodeID]*FileEntry{},
+		fs:          fs,
+		//client:  fs.Client,
+		inodeMU: &sync.Mutex{},
+		uid:     fs.Config.UID,
+		gid:     fs.Config.GID,
+	}
+	_, rootEntry := fc.FileEntry(nil, fuseops.RootInodeID)
+	rootEntry.Attr.Mode = os.FileMode(0755) | os.ModeDir
+
+	return fc
+}
+
+func (fc *FileContainer) Count() int {
+	return len(fc.fileEntries)
+}
+
+func (fc *FileContainer) FindByInode(inode fuseops.InodeID) *FileEntry {
+	return fc.fileEntries[inode]
+}
+
+// GID specific functions
+func (fc *FileContainer) FindByGID(gid string) (fuseops.InodeID, *FileEntry) {
+	for inode, v := range fc.fileEntries {
+		if v.File != nil && v.File.ID() == gid {
+			return inode, v
+		}
+	}
+	return 0, nil
+}
+
+func (fc *FileContainer) LookupByGID(parentGID string, name string) (fuseops.InodeID, *FileEntry) {
+
+	for inode, entry := range fc.fileEntries {
+		if entry.HasParentGID(parentGID) && entry.Name == name {
+			return inode, entry
+		}
+	}
+	return 0, nil
+
+}
+func (fc *FileContainer) Lookup(parent *FileEntry, name string) (fuseops.InodeID, *FileEntry) {
+	for inode, entry := range fc.fileEntries {
+		if entry.HasParent(parent) && entry.Name == name {
+			return inode, entry
+		}
+	}
+	return 0, nil
+}
+
+func (fc *FileContainer) ListByParent(parent *FileEntry) map[fuseops.InodeID]*FileEntry {
+	ret := map[fuseops.InodeID]*FileEntry{}
+	for inode, entry := range fc.fileEntries {
+		if entry.HasParent(parent) {
+			ret[inode] = entry
+		}
+	}
+	return ret
+
+}
+
+func (fc *FileContainer) CreateFile(parentFile *FileEntry, name string, isDir bool) (fuseops.InodeID, *FileEntry, error) {
+
+	newGFile := &drive.File{
+		Parents: []string{parentFile.File.ID()},
+		Name:    name,
+	}
+	if isDir {
+		newGFile.MimeType = "application/vnd.google-apps.folder"
+	}
+	// Could be transformed to CreateFile in continer
+	createdGFile, err := fc.fs.Client.Files.Create(newGFile).Fields(fileFields).Do()
+	if err != nil {
+		return 0, nil, fuse.EINVAL
+	}
+	inode, entry := fc.FileEntry(createdGFile) // New Entry added // Or Return same?
+
+	return inode, entry, nil
+}
+
+func (fc *FileContainer) DeleteFile(entry *FileEntry) error {
+	err := fc.fs.Client.Files.Delete(entry.File.ID()).Do()
+	if err != nil {
+		return fuse.EIO
+	}
+
+	fc.RemoveEntry(entry)
+	return nil
+}
+
+//////////////
+
+//Return or create inode // Pass name maybe?
+func (fc *FileContainer) FileEntry(gfile *drive.File, inodeOps ...fuseops.InodeID) (fuseops.InodeID, *FileEntry) {
+
+	fc.inodeMU.Lock()
+	defer fc.inodeMU.Unlock()
+
+	var inode fuseops.InodeID
+	if len(inodeOps) > 0 {
+		inode = inodeOps[0]
+		if fe, ok := fc.fileEntries[inode]; ok {
+			return inode, fe
+		}
+	} else { // generate new inode
+		// Max Inode Number
+		for inode = 2; inode < 99999; inode++ {
+			_, ok := fc.fileEntries[inode]
+			if !ok {
+				break
+			}
+		}
+	}
+
+	name := ""
+	if gfile != nil {
+		name = gfile.Name
+		count := 1
+		nameParts := strings.Split(name, ".")
+		for {
+			// We find if we have a GFile in same parent with same name
+			var entry *FileEntry
+			// Only Place requireing a GID
+			for _, p := range gfile.Parents {
+				_, entry = fc.LookupByGID(p, name)
+				if entry != nil {
+					break
+				}
+			}
+			if entry == nil { // Not found return
+				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)
+			}
+			log.Printf("Conflicting name generated new '%s' as '%s'", gfile.Name, name)
+		}
+	}
+
+	fe := &FileEntry{
+		//Inode: inode,
+		//container: fc,
+		Name: name,
+		//children:  []*FileEntry{},
+		Attr: fuseops.InodeAttributes{
+			Uid: fc.uid,
+			Gid: fc.gid,
+		},
+	}
+	fe.SetFile(&GFile{gfile}, fc.uid, fc.gid)
+	fc.fileEntries[inode] = fe
+
+	return inode, fe
+}
+
+func (fc *FileContainer) SetEntry(inode fuseops.InodeID, entry *FileEntry) {
+	fc.fileEntries[inode] = entry
+}
+
+// RemoveEntry remove file entry
+func (fc *FileContainer) RemoveEntry(entry *FileEntry) {
+	var inode fuseops.InodeID
+	for k, e := range fc.fileEntries {
+		if e == entry {
+			inode = k
+		}
+	}
+	delete(fc.fileEntries, inode)
+}
+
+func (fc *FileContainer) Sync(fe *FileEntry) (err error) {
+	if fe.tempFile == nil {
+		return
+	}
+	fe.tempFile.Sync()
+	fe.tempFile.Seek(0, io.SeekStart)
+
+	upFile, err := fc.fs.Service.Upload(fe.tempFile, fe.File)
+	if err != nil {
+		return
+	}
+	fe.SetFile(upFile, fc.uid, fc.gid) // update local GFile entry
+	return
+
+}
+
+//ClearCache remove local file
+func (fc *FileContainer) ClearCache(fe *FileEntry) (err error) {
+	if fe.tempFile == nil {
+		return
+	}
+	fe.tempFile.Close()
+	os.Remove(fe.tempFile.Name())
+	fe.tempFile = nil
+	return
+}
+
+// Cache download GDrive file to a temporary local file or return already created file
+func (fc *FileContainer) Cache(fe *FileEntry) *os.File {
+	if fe.tempFile != nil {
+		return fe.tempFile
+	}
+	var err error
+
+	// Local copy
+	fe.tempFile, err = ioutil.TempFile(os.TempDir(), "gdfs") // TODO: const this elsewhere
+	if err != nil {
+		log.Println("Error creating temp file")
+		return nil
+	}
+	err = fc.fs.Service.DownloadTo(fe.tempFile, fe.File)
+	if err != nil {
+		return nil
+	}
+	fe.tempFile.Seek(0, io.SeekStart)
+	return fe.tempFile
+
+}
+
+func (fc *FileContainer) Truncate(fe *FileEntry) (err error) { // DriverTruncate
+	// Delete and create another on truncate 0
+	newFile, err := fc.fs.Service.Truncate(fe.File)
+
+	if err != nil {
+		return fuse.EINVAL
+	}
+	fe.SetFile(newFile, fc.uid, fc.gid) // Set new file
+	return
+}

+ 75 - 0
internal/fs/basefs/file_entry.go

@@ -0,0 +1,75 @@
+package basefs
+
+import (
+	"os"
+
+	"github.com/jacobsa/fuse/fuseops"
+)
+
+//FileEntry entry to handle files
+type FileEntry struct {
+	//Inode fuseops.InodeID
+	GID      string // google driveID
+	File     File
+	Name     string                  // local name
+	Attr     fuseops.InodeAttributes // Cached attributes
+	tempFile *os.File                // Cached file
+}
+
+// Why?
+func (fe *FileEntry) HasParent(parent *FileEntry) bool {
+
+	// Exceptional case
+	/*if fe.Inode == fuseops.RootInodeID {
+		return false
+	}*/
+	if parent.GID == "" && fe.File == nil && len(fe.File.Parents()) == 0 { // We are looking in root
+		return true
+	}
+
+	if fe.File == nil { // Case gid is not empty and GFile is nil
+		return false
+	}
+	for _, pgid := range fe.File.Parents() {
+		if pgid == parent.GID {
+			return true
+		}
+	}
+	return false
+}
+func (fe *FileEntry) HasParentGID(parentGID string) bool {
+
+	// Exceptional case
+	/*if fe.Inode == fuseops.RootInodeID {
+		return false
+	}*/
+	if parentGID == "" && fe.File == nil && len(fe.File.Parents()) == 0 { // We are looking in root
+		return true
+	}
+	if fe.File == nil { // Case gid is not empty and GFile is null
+		return false
+	}
+	for _, pgid := range fe.File.Parents() {
+		if pgid == parentGID {
+			return true
+		}
+	}
+	return false
+}
+
+// SetGFile update attributes and set drive.File
+func (fe *FileEntry) SetFile(file File, uid, gid uint32) { // Should remove from here maybe?
+	fe.File = file
+
+	// GetAttribute from GFile somehow
+	// Create Attribute
+	fe.Attr = file.Attr()
+	//fe.Attr.Uid = fe.container.uid
+	//fe.Attr.Gid = fe.container.gid
+}
+
+// Sync cached , upload to gdrive
+// IsDir returns true if entry is a directory:w
+func (fe *FileEntry) IsDir() bool {
+	return fe.Attr.Mode&os.ModeDir == os.ModeDir
+}

+ 47 - 0
internal/fs/basefs/gfile.go

@@ -0,0 +1,47 @@
+// Tempoarry in basefs since we dont have the service yet
+package basefs
+
+import (
+	"os"
+	"time"
+
+	"github.com/jacobsa/fuse/fuseops"
+	drive "google.golang.org/api/drive/v3"
+)
+
+type GFile struct {
+	*drive.File
+}
+
+func (gf *GFile) ID() string {
+	return gf.Id
+}
+
+func (gf *GFile) Name() string {
+	return gf.File.Name
+}
+
+func (gf *GFile) Parents() []string {
+
+	return gf.File.Parents
+}
+
+func (gf *GFile) Attr() fuseops.InodeAttributes {
+
+	attr := fuseops.InodeAttributes{}
+	attr.Nlink = 1
+	//attr.Uid = fe.container.uid
+	//attr.Gid = fe.container.gid
+
+	attr.Mode = os.FileMode(0644) // default
+	//if gfile != nil {
+	attr.Size = uint64(gf.File.Size)
+	attr.Crtime, _ = time.Parse(time.RFC3339, gf.File.CreatedTime)
+	attr.Ctime = attr.Crtime // Set CTime to created, although it is change inode metadata
+	attr.Mtime, _ = time.Parse(time.RFC3339, gf.File.ModifiedTime)
+	attr.Atime = attr.Mtime // Set access time to modified, not sure if gdrive has access time
+	if gf.MimeType == "application/vnd.google-apps.folder" {
+		attr.Mode = os.FileMode(0755) | os.ModeDir
+	}
+	return attr
+}

+ 9 - 0
internal/fs/basefs/service.go

@@ -0,0 +1,9 @@
+package basefs
+
+import "io"
+
+type Service interface {
+	Truncate(file File) (File, error)
+	Upload(reader io.Reader, file File) (File, error)
+	DownloadTo(w io.Writer, file File) error
+}

+ 651 - 0
internal/fs/dropboxfs/dropboxfs.go

@@ -0,0 +1,651 @@
+// gdrivemount implements a google drive fuse driver
+package dropboxfs
+
+import (
+	"io"
+	"os"
+	"sync"
+	"syscall"
+	"time"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+	"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"
+	"github.com/stacktic/dropbox"
+)
+
+var (
+	log = prettylog.New("gdrivefs")
+)
+
+type Handle struct {
+	ID fuseops.HandleID
+	//entry        *FileEntry
+	uploadOnDone bool
+	// Handling for dir
+	entries []fuseutil.Dirent
+}
+
+// GDriveFS
+type GDriveFS struct {
+	fuseutil.NotImplementedFileSystem // Defaults
+
+	config *core.Config //core   *core.Core // Core Config instead?
+	//serviceConfig *Config
+	client *dropbox.Dropbox
+	//root   *FileEntry // hiearchy reference
+	root *FileContainer
+
+	fileHandles map[fuseops.HandleID]*Handle
+	//fileEntries map[fuseops.InodeID]*FileEntry
+
+	nextRefresh time.Time
+
+	handleMU *sync.Mutex
+	//fileMap map[string]
+	// Map IDS with FileEntries
+}
+
+func New(core *core.Core) core.Driver {
+
+	fs := &GDriveFS{
+		config:        &core.Config,
+		serviceConfig: &Config{},
+		fileHandles:   map[fuseops.HandleID]*Handle{},
+		handleMU:      &sync.Mutex{},
+	}
+	fs.initClient() // Init Oauth2 client
+	fs.root = NewFileContainer(fs)
+	fs.root.uid = core.Config.UID
+	fs.root.gid = core.Config.GID
+
+	//fs.root = rootEntry
+
+	// Temporary entry
+	entry := fs.root.FileEntry(&drive.File{Id: "0", Name: "Loading..."}, 9999)
+	entry.Attr.Mode = os.FileMode(0)
+
+	return fs
+}
+
+// Async
+func (fs *GDriveFS) Start() {
+	go func() {
+		fs.Refresh() // First load
+
+		// Change reader loop
+		startPageTokenRes, err := fs.client.Changes.GetStartPageToken().Do()
+		if err != nil {
+			log.Println("GDrive err", err)
+		}
+		savedStartPageToken := startPageTokenRes.StartPageToken
+		for {
+			pageToken := savedStartPageToken
+			for pageToken != "" {
+				changesRes, err := fs.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
+				if err != nil {
+					log.Println("Err fetching changes", err)
+					break
+				}
+				//log.Println("Changes:", len(changesRes.Changes))
+				for _, c := range changesRes.Changes {
+					entry := fs.root.FindByGID(c.FileId)
+					if c.Removed {
+						if entry == nil {
+							continue
+						} else {
+							fs.root.RemoveEntry(entry)
+						}
+						continue
+					}
+
+					if entry != nil {
+						entry.SetGFile(c.File)
+					} else {
+						//Create new one
+						fs.root.FileEntry(c.File) // Creating new one
+					}
+				}
+				if changesRes.NewStartPageToken != "" {
+					savedStartPageToken = changesRes.NewStartPageToken
+				}
+				pageToken = changesRes.NextPageToken
+			}
+
+			time.Sleep(fs.config.RefreshTime)
+		}
+	}()
+}
+
+////////////////////////////////////////////////////////
+// TOOLS & HELPERS
+////////////////////////////////////////////////////////
+
+func (fs *GDriveFS) createHandle() *Handle {
+	// Lock here instead
+	fs.handleMU.Lock()
+	defer fs.handleMU.Unlock()
+
+	var handleID fuseops.HandleID
+
+	for handleID = 1; handleID < 99999; handleID++ {
+		_, ok := fs.fileHandles[handleID]
+		if !ok {
+			break
+		}
+	}
+
+	handle := &Handle{ID: handleID}
+	fs.fileHandles[handleID] = handle
+
+	return handle
+}
+
+const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
+const gdFields = googleapi.Field("files(" + fileFields + ")")
+
+// FULL Refresh service files
+func (fs *GDriveFS) Refresh() {
+	fs.nextRefresh = time.Now().Add(1 * time.Minute)
+
+	fileList := []*drive.File{}
+	fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
+
+	r, err := fs.client.Files.List().
+		OrderBy("createdTime").
+		PageSize(1000).
+		SupportsTeamDrives(true).
+		IncludeTeamDriveItems(true).
+		Fields(googleapi.Field("nextPageToken"), gdFields).
+		Do()
+	if err != nil {
+		// Sometimes gdrive returns error 500 randomly
+		log.Println("GDrive ERR:", err)
+		fs.Refresh() // retry
+		return
+	}
+	fileList = append(fileList, r.Files...)
+
+	// Rest of the pages
+	for r.NextPageToken != "" {
+		r, err = fs.client.Files.List().
+			OrderBy("createdTime").
+			PageToken(r.NextPageToken).
+			Fields(googleapi.Field("nextPageToken"), gdFields).
+			Do()
+		if err != nil {
+			log.Println("GDrive ERR:", err)
+			fs.Refresh() // retry // Same as above
+			return
+		}
+		fileList = append(fileList, r.Files...)
+	}
+	log.Println("Total entries:", len(fileList))
+
+	// Cache ID for faster retrieval, might not be necessary
+	for _, f := range fileList {
+		fileMap[f.Id] = f
+	}
+
+	if err != nil || r == nil {
+		log.Println("Unable to retrieve files", err)
+		return
+	}
+
+	// Create clean fileList
+	root := NewFileContainer(fs)
+	var appendFile func(gfile *drive.File)
+	appendFile = func(gfile *drive.File) {
+		for _, pID := range gfile.Parents {
+			parentFile, ok := fileMap[pID]
+			if !ok {
+				parentFile, err = fs.client.Files.Get(pID).Do()
+				if err != nil {
+					log.Println("Error fetching single file:", err)
+				}
+				fileMap[parentFile.Id] = parentFile
+			}
+			appendFile(parentFile) // Recurse
+		}
+
+		// Find existing entry
+		entry := fs.root.FindByGID(gfile.Id)
+		// Store for later add
+		if entry == nil {
+			entry = fs.root.FileEntry(gfile) // Add New and retrieve
+		}
+		root.AddEntry(entry)
+		// add File
+	}
+
+	for _, f := range fileList { // Ordered
+		appendFile(f) // Check parent first
+	}
+
+	log.Println("Refresh done, update root")
+	fs.root = root
+	//fs.root.children = root.children
+
+	log.Println("File count:", len(root.fileEntries))
+}
+
+///////////////////////////////
+// Fuse operations
+////////////
+
+// 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)
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	handle := fs.createHandle()
+	handle.entry = entry
+	op.Handle = handle.ID
+
+	return // No error allow, dir open
+}
+
+// ReadDir lists files into readdirop
+func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
+	fh, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		log.Fatal("Handle does not exists")
+	}
+
+	if op.Offset == 0 { // Rebuild/rewind dir list
+		fh.entries = []fuseutil.Dirent{}
+		children := fs.root.ListByParentGID(fh.entry.GID)
+
+		for i, v := range children {
+			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)
+		}
+	}
+
+	index := int(op.Offset)
+	if index > len(fh.entries) {
+		return fuse.EINVAL
+	}
+	if index > 0 {
+		index++
+	}
+	for i := index; i < len(fh.entries); i++ {
+		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
+		if n == 0 {
+			break
+		}
+		op.BytesRead += n
+	}
+	return
+}
+
+// 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) {
+
+	// Hack to truncate file?
+
+	if op.Size != nil {
+		f := fs.root.FindByInode(op.Inode)
+
+		if *op.Size != 0 { // We only allow truncate to 0
+			return fuse.ENOSYS
+		}
+		// Delete and create another on truncate 0
+		err = fs.client.Files.Delete(f.GFile.Id).Do() // XXX: Careful on this
+		createdFile, err := fs.client.Files.Create(&drive.File{Parents: f.GFile.Parents, Name: f.GFile.Name}).Fields(fileFields).Do()
+		if err != nil {
+			return fuse.EINVAL
+		}
+		f.SetGFile(createdFile) // Set new file
+	}
+
+	return
+}
+
+//GetInodeAttributes return attributes
+func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
+
+	f := fs.root.FindByInode(op.Inode)
+	if f == nil {
+		return fuse.ENOENT
+	}
+	op.Attributes = f.Attr
+	op.AttributesExpiration = time.Now().Add(time.Minute)
+
+	return
+}
+
+// ReleaseDirHandle deletes file handle entry
+func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
+	delete(fs.fileHandles, op.Handle)
+	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) {
+
+	parentFile := fs.root.FindByInode(op.Parent) // true means transverse all
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+
+	entry := fs.root.LookupByGID(parentFile.GID, op.Name)
+
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	// Transverse only local
+
+	now := time.Now()
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: now.Add(time.Second),
+		EntryExpiration:      now.Add(time.Second),
+	}
+	return
+}
+
+// StatFS basically allows StatFS to run
+/*func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
+	return
+}*/
+
+// ForgetInode allows to forgetInode
+func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
+	return
+}
+
+// GetXAttr special attributes
+func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
+	return
+}
+
+//////////////////////////////////////////////////////////////////////////
+// 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) {
+	f := fs.root.FindByInode(op.Inode) // might not exists
+
+	// Generate new handle
+	handle := fs.createHandle()
+	handle.entry = f
+
+	op.Handle = handle.ID
+	op.UseDirectIO = true
+
+	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) {
+	handle := fs.fileHandles[op.Handle]
+
+	localFile := handle.entry.Cache()
+	op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
+	if err == io.EOF { // fuse does not expect a EOF
+		err = nil
+	}
+
+	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) {
+
+	parentFile := fs.root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	// Only write on child folders
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	existsFile := fs.root.LookupByGID(parentFile.GID, op.Name)
+	//existsFile := parentFile.FindByName(op.Name, false)
+	if existsFile != nil {
+		return fuse.EEXIST
+	}
+
+	newGFile := &drive.File{
+		Parents: []string{parentFile.GFile.Id},
+		Name:    op.Name,
+	}
+
+	createdGFile, err := fs.client.Files.Create(newGFile).Fields(fileFields).Do()
+	if err != nil {
+		err = fuse.EINVAL
+		return
+	}
+	entry := fs.root.FileEntry(createdGFile) // New Entry added // Or Return same?
+
+	// Associate a temp file to a new handle
+	// Local copy
+	// Lock
+	handle := fs.createHandle()
+	handle.entry = entry
+	handle.uploadOnDone = true
+	//
+	op.Handle = handle.ID
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Minute),
+	}
+	op.Mode = entry.Attr.Mode
+
+	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) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+
+	localFile := handle.entry.Cache()
+	if localFile == nil {
+		return fuse.EINVAL
+	}
+
+	_, err = localFile.WriteAt(op.Data, op.Offset)
+	if err != nil {
+		err = fuse.EIO
+		return
+	}
+	handle.uploadOnDone = true
+
+	return
+}
+
+// FlushFile just returns no error, maybe upload should be handled here
+func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+	if handle.entry.tempFile == nil {
+		return
+	}
+	if handle.uploadOnDone { // or if content changed basically
+		err = handle.entry.Sync()
+		if err != nil {
+			return fuse.EINVAL
+		}
+	}
+	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) {
+	handle := fs.fileHandles[op.Handle]
+
+	handle.entry.ClearCache()
+
+	delete(fs.fileHandles, op.Handle)
+
+	return
+}
+
+// Unlink remove file and remove from local cache entry
+func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
+	parentEntry := fs.root.FindByInode(op.Parent)
+	if parentEntry == nil {
+		return fuse.ENOENT
+	}
+	if parentEntry.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	fileEntry := fs.root.LookupByGID(parentEntry.GID, op.Name)
+	//fileEntry := parentEntry.FindByName(op.Name, false)
+	if fileEntry == nil {
+		return fuse.ENOATTR
+	}
+	err = fs.client.Files.Delete(fileEntry.GFile.Id).Do()
+	if err != nil {
+		return fuse.EIO
+	}
+
+	fs.root.RemoveEntry(fileEntry)
+	//parentEntry.RemoveChild(fileEntry)
+
+	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)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	// Should check existent first too
+	createdGFile, err := fs.client.Files.Create(&drive.File{
+		Parents:  []string{parentFile.GFile.Id},
+		MimeType: "application/vnd.google-apps.folder",
+		Name:     op.Name,
+	}).Fields(fileFields).Do()
+	if err != nil {
+		return fuse.ENOATTR
+	}
+	entry := fs.root.FileEntry(createdGFile)
+	//entry = parentFile.AppendGFile(fi, entry.Inode)
+	//if entry == nil {
+	//		return fuse.EINVAL
+	//	}
+
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Microsecond),
+	}
+
+	return
+}
+
+// RmDir fuse implementation
+func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
+
+	parentFile := fs.root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	theFile := fs.root.LookupByGID(parentFile.GID, op.Name)
+	//theFile := parentFile.FindByName(op.Name, false)
+
+	err = fs.client.Files.Delete(theFile.GFile.Id).Do()
+	if err != nil {
+		return fuse.ENOTEMPTY
+	}
+	fs.root.RemoveEntry(theFile)
+
+	//parentFile.RemoveChild(theFile)
+
+	// Remove from entry somehow
+
+	return
+}
+
+// Rename fuse implementation
+func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
+	oldParentFile := fs.root.FindByInode(op.OldParent)
+	if oldParentFile == nil {
+		return fuse.ENOENT
+	}
+	newParentFile := fs.root.FindByInode(op.NewParent)
+	if newParentFile == nil {
+		return fuse.ENOENT
+	}
+
+	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	//oldFile := oldParentFile.FindByName(op.OldName, false)
+	oldEntry := fs.root.LookupByGID(oldParentFile.GID, op.OldName)
+
+	// Although GDrive allows duplicate names, there is some issue with inode caching
+	// So we prevent a rename to a file with same name
+	//existsFile := newParentFile.FindByName(op.NewName, false)
+	existsEntry := fs.root.LookupByGID(newParentFile.GID, op.NewName)
+	if existsEntry != nil {
+		return fuse.EEXIST
+	}
+
+	ngFile := &drive.File{
+		Name: op.NewName,
+	}
+
+	updateCall := fs.client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
+	if oldParentFile != newParentFile {
+		updateCall.RemoveParents(oldParentFile.GID)
+		updateCall.AddParents(newParentFile.GID)
+	}
+	updatedFile, err := updateCall.Do()
+
+	oldEntry.SetGFile(updatedFile)
+
+	//oldParentFile.RemoveChild(oldFile)
+	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
+
+	return
+
+}

+ 68 - 0
internal/fs/dropboxfs/research/main.go

@@ -0,0 +1,68 @@
+package main
+
+import (
+	"log"
+
+	"github.com/stacktic/dropbox"
+)
+
+func main() {
+	var err error
+	var db *dropbox.Dropbox
+
+	clientid := "ycsj9tcm7bqdutw"
+	clientsecret := "rtlg6f5jfgau80s"
+	//token := "Iw62s3Vg5sAAAAAAAAAuPeAaIstFQmi3iJ659RMTlaL_xcV7FPnYMMwtNdMNEII5"
+	token := "Iw62s3Vg5sAAAAAAAAAuP-725bDtvizWhH-OhxyvxjSgeNXYxIrL44siRqpw4ZNJ"
+
+	// 1. Create a new dropbox object.
+	db = dropbox.NewDropbox()
+
+	// 2. Provide your clientid and clientsecret (see prerequisite).
+	db.SetAppInfo(clientid, clientsecret)
+
+	// 3. Provide the user token.
+	// This method will ask the user to visit an URL and paste the generated code.
+	/*if err = db.Auth(); err != nil {
+		fmt.Println(err)
+		return
+	}
+	token := db.AccessToken() // You can now retrieve the token if you want.*/
+	log.Println("Tok:", token)
+	db.SetAccessToken(token)
+
+	// 4. Send your commands.
+	// In this example, you will create a new folder named "demo".
+	log.Println("Commands")
+	acct, err := db.GetAccountInfo()
+	errCheck(err)
+	log.Println(acct.Country)
+	log.Println(acct.DisplayName)
+	log.Println(acct.QuotaInfo.Normal)
+
+	entry, err := db.Metadata("/", true, false, "", "", 0)
+	errCheck(err)
+	log.Println("Entries:", len(entry.Contents))
+
+	for _, e := range entry.Contents {
+		log.Println("Entry:", e.Hash, e.Path, e.Bytes)
+	}
+
+	dm := db.NewDatastoreManager()
+	ds, err := dm.ListDatastores()
+	errCheck(err)
+	log.Println("Data stores:", ds)
+
+	//	folder := "demo"
+	/*if _, err = db.CreateFolder(folder); err != nil {
+		fmt.Printf("Error creating folder %s: %s\n", folder, err)
+	} else {
+		fmt.Printf("Folder %s successfully created\n", folder)
+	}*/
+}
+
+func errCheck(err error) {
+	if err != nil {
+		log.Fatal(err)
+	}
+}

+ 14 - 10
internal/fs/gdrivefs/client.go

@@ -38,16 +38,16 @@ type the authorization code: `, authURL)
 }
 
 // Init driveService
-func (d *GDriveFS) initClient() {
+func (fs *GDriveFS) initClient() *drive.Service {
 
 	//configPath := d.config.HomeDir
 
 	ctx := context.Background() // Context from GDriveFS
 
 	log.Println("Initializing gdrive service")
-	log.Println("Source config:", d.config.Source)
+	log.Println("Source config:", fs.Config.Source)
 
-	err := core.ParseConfig(d.config.Source, d.serviceConfig)
+	err := core.ParseConfig(fs.Config.Source, fs.serviceConfig)
 
 	//b, err := ioutil.ReadFile(d.config.Source)
 
@@ -56,8 +56,8 @@ func (d *GDriveFS) initClient() {
 		log.Fatalf("Unable to read client secret file: %v", err)
 	}
 	config := &oauth2.Config{
-		ClientID:     d.serviceConfig.ClientSecret.ClientID,
-		ClientSecret: d.serviceConfig.ClientSecret.ClientSecret,
+		ClientID:     fs.serviceConfig.ClientSecret.ClientID,
+		ClientSecret: fs.serviceConfig.ClientSecret.ClientSecret,
 		RedirectURL:  "urn:ietf:wg:oauth:2.0:oob", //d.serviceConfig.ClientSecret.RedirectURIs[0],
 		Scopes:       []string{drive.DriveScope},
 		Endpoint: oauth2.Endpoint{
@@ -67,10 +67,10 @@ func (d *GDriveFS) initClient() {
 	}
 	// We can deal with oauthToken here too
 
-	if d.serviceConfig.Auth == nil {
+	if fs.serviceConfig.Auth == nil {
 		tok := getTokenFromWeb(config)
-		d.serviceConfig.Auth = tok
-		core.SaveConfig(d.config.Source, d.serviceConfig)
+		fs.serviceConfig.Auth = tok
+		core.SaveConfig(fs.Config.Source, fs.serviceConfig)
 	}
 
 	/*config, err := google.ConfigFromJSON(b, drive.DriveScope)
@@ -78,10 +78,14 @@ func (d *GDriveFS) initClient() {
 		log.Fatalf("Unable to parse client secret file: %v", err)
 	}*/
 
-	client := config.Client(ctx, d.serviceConfig.Auth)
-	d.client, err = drive.New(client)
+	client := config.Client(ctx, fs.serviceConfig.Auth)
+	service, err := drive.New(client)
 	if err != nil {
 		log.Fatalf("Unable to retrieve drive Client: %v", err)
 	}
 
+	//d.client = service
+
+	return service
+
 }

+ 0 - 162
internal/fs/gdrivefs/file_container.go

@@ -1,162 +0,0 @@
-package gdrivefs
-
-import (
-	"fmt"
-	"os"
-	"strings"
-	"sync"
-
-	drive "google.golang.org/api/drive/v3"
-
-	"github.com/jacobsa/fuse/fuseops"
-)
-
-type FileContainer struct {
-	fileEntries map[fuseops.InodeID]*FileEntry
-	tree        *FileEntry
-	fs          *GDriveFS
-	uid         uint32
-	gid         uint32
-
-	inodeMU *sync.Mutex
-}
-
-func NewFileContainer(fs *GDriveFS) *FileContainer {
-	fc := &FileContainer{
-		fileEntries: map[fuseops.InodeID]*FileEntry{},
-		fs:          fs,
-		inodeMU:     &sync.Mutex{},
-		uid:         fs.config.UID,
-		gid:         fs.config.GID,
-	}
-	rootEntry := fc.FileEntry(nil, fuseops.RootInodeID)
-	rootEntry.Attr.Mode = os.FileMode(0755) | os.ModeDir
-	fc.tree = rootEntry
-
-	return fc
-}
-
-func (fc *FileContainer) FindByInode(inode fuseops.InodeID) *FileEntry {
-	return fc.fileEntries[inode]
-}
-
-func (fc *FileContainer) FindByGID(gid string) *FileEntry {
-	for _, v := range fc.fileEntries {
-		if v.GFile != nil && v.GFile.Id == gid {
-			return v
-		}
-	}
-	return nil
-}
-
-func (fc *FileContainer) LookupByGID(parentGID string, name string) *FileEntry {
-	for _, entry := range fc.fileEntries {
-		if entry.HasParentGID(parentGID) && entry.Name == name {
-			return entry
-		}
-	}
-	return nil
-}
-
-func (fc *FileContainer) ListByParentGID(parentGID string) []*FileEntry {
-	ret := []*FileEntry{}
-	for _, entry := range fc.fileEntries {
-		if entry.HasParentGID(parentGID) {
-			ret = append(ret, entry)
-		}
-	}
-	return ret
-}
-
-//Return or create inode // Pass name maybe?
-func (fc *FileContainer) FileEntry(gfile *drive.File, inodeOps ...fuseops.InodeID) *FileEntry {
-
-	fc.inodeMU.Lock()
-	defer fc.inodeMU.Unlock()
-
-	var inode fuseops.InodeID
-	if len(inodeOps) > 0 {
-		inode = inodeOps[0]
-		if fe, ok := fc.fileEntries[inode]; ok {
-			return fe
-		}
-	} else { // generate new inode
-		// Max Inode Number
-		for inode = 2; inode < 99999; inode++ {
-			_, ok := fc.fileEntries[inode]
-			if !ok {
-				break
-			}
-		}
-	}
-
-	name := ""
-	if gfile != nil {
-		name = gfile.Name
-		count := 1
-		nameParts := strings.Split(name, ".")
-		for {
-			// We find if we have a GFile in same parent with same name
-			var entry *FileEntry
-			for _, p := range gfile.Parents {
-				entry = fc.LookupByGID(p, name)
-				if entry != nil {
-					break
-				}
-			}
-			if entry == nil { // Not found return
-				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)
-			}
-			log.Printf("Conflicting name generated new '%s' as '%s'", gfile.Name, name)
-		}
-	}
-
-	fe := &FileEntry{
-		GFile:     gfile,
-		Inode:     inode,
-		container: fc,
-		Name:      name,
-		//children:  []*FileEntry{},
-		Attr: fuseops.InodeAttributes{
-			Uid: fc.uid,
-			Gid: fc.gid,
-		},
-	}
-	fe.SetGFile(gfile)
-	fc.fileEntries[inode] = fe
-
-	return fe
-}
-
-func (fc *FileContainer) AddEntry(entry *FileEntry) {
-	fc.fileEntries[entry.Inode] = entry
-}
-
-// RemoveEntry remove file entry
-func (fc *FileContainer) RemoveEntry(entry *FileEntry) {
-	var inode fuseops.InodeID
-	for k, e := range fc.fileEntries {
-		if e == entry {
-			inode = k
-		}
-	}
-	delete(fc.fileEntries, inode)
-}
-
-func (fc *FileContainer) AddGFile(gfile *drive.File) *FileEntry {
-	entry := fc.FindByGID(gfile.Id)
-	if entry != nil {
-		return entry
-	}
-	// Create new Entry
-	entry = fc.FileEntry(gfile)
-	entry.SetGFile(gfile)
-
-	return entry
-}

+ 26 - 509
internal/fs/gdrivefs/gdrivefs.go

@@ -1,87 +1,43 @@
-// gdrivemount implements a google drive fuse driver
 package gdrivefs
 
 import (
-	"io"
-	"os"
-	"sync"
-	"syscall"
 	"time"
 
-	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
-	"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"
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
+	"dev.hexasoftware.com/hxs/prettylog"
 )
 
 var (
 	log = prettylog.New("gdrivefs")
 )
 
-type Handle struct {
-	ID           fuseops.HandleID
-	entry        *FileEntry
-	uploadOnDone bool
-	// Handling for dir
-	entries []fuseutil.Dirent
-}
-
-// GDriveFS
 type GDriveFS struct {
-	fuseutil.NotImplementedFileSystem // Defaults
-
-	config        *core.Config //core   *core.Core // Core Config instead?
+	*basefs.BaseFS
 	serviceConfig *Config
-	client        *drive.Service
-	//root   *FileEntry // hiearchy reference
-	root *FileContainer
-
-	fileHandles map[fuseops.HandleID]*Handle
-	fileEntries map[fuseops.InodeID]*FileEntry
-
-	nextRefresh time.Time
-
-	handleMU *sync.Mutex
-	//fileMap map[string]
-	// Map IDS with FileEntries
+	nextRefresh   time.Time
 }
 
-func New(core *core.Core) core.Driver {
-
-	fs := &GDriveFS{
-		config:        &core.Config,
-		serviceConfig: &Config{},
-		fileHandles:   map[fuseops.HandleID]*Handle{},
-		handleMU:      &sync.Mutex{},
-	}
-	fs.initClient() // Init Oauth2 client
-	fs.root = NewFileContainer(fs)
-	fs.root.uid = core.Config.UID
-	fs.root.gid = core.Config.GID
+func New(core *core.Core) core.DriverFS {
 
-	//fs.root = rootEntry
+	fs := &GDriveFS{basefs.New(core), &Config{}, time.Now()}
+	client := fs.initClient() // Init Oauth2 client
 
-	// Temporary entry
-	entry := fs.root.FileEntry(&drive.File{Id: "0", Name: "Loading..."}, 9999)
-	entry.Attr.Mode = os.FileMode(0)
+	fs.BaseFS.Client = client // This will be removed
+	fs.BaseFS.Service = &gdriveService{client}
 
 	return fs
 }
 
-// Async
 func (fs *GDriveFS) Start() {
 	go func() {
 		fs.Refresh() // First load
 
 		// Change reader loop
-		startPageTokenRes, err := fs.client.Changes.GetStartPageToken().Do()
+		startPageTokenRes, err := fs.Client.Changes.GetStartPageToken().Do()
 		if err != nil {
 			log.Println("GDrive err", err)
 		}
@@ -89,28 +45,28 @@ func (fs *GDriveFS) Start() {
 		for {
 			pageToken := savedStartPageToken
 			for pageToken != "" {
-				changesRes, err := fs.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
+				changesRes, err := fs.Client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
 				if err != nil {
 					log.Println("Err fetching changes", err)
 					break
 				}
 				//log.Println("Changes:", len(changesRes.Changes))
 				for _, c := range changesRes.Changes {
-					entry := fs.root.FindByGID(c.FileId)
+					_, entry := fs.Root.FindByGID(c.FileId)
 					if c.Removed {
 						if entry == nil {
 							continue
 						} else {
-							fs.root.RemoveEntry(entry)
+							fs.Root.RemoveEntry(entry)
 						}
 						continue
 					}
 
 					if entry != nil {
-						entry.SetGFile(c.File)
+						entry.SetFile(&basefs.GFile{c.File}, fs.Config.UID, fs.Config.GID)
 					} else {
 						//Create new one
-						fs.root.FileEntry(c.File) // Creating new one
+						fs.Root.FileEntry(c.File) // Creating new one
 					}
 				}
 				if changesRes.NewStartPageToken != "" {
@@ -119,46 +75,20 @@ func (fs *GDriveFS) Start() {
 				pageToken = changesRes.NextPageToken
 			}
 
-			time.Sleep(fs.config.RefreshTime)
+			time.Sleep(fs.Config.RefreshTime)
 		}
 	}()
 }
 
-////////////////////////////////////////////////////////
-// TOOLS & HELPERS
-////////////////////////////////////////////////////////
-
-func (fs *GDriveFS) createHandle() *Handle {
-	// Lock here instead
-	fs.handleMU.Lock()
-	defer fs.handleMU.Unlock()
-
-	var handleID fuseops.HandleID
-
-	for handleID = 1; handleID < 99999; handleID++ {
-		_, ok := fs.fileHandles[handleID]
-		if !ok {
-			break
-		}
-	}
-
-	handle := &Handle{ID: handleID}
-	fs.fileHandles[handleID] = handle
-
-	return handle
-}
-
 const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
 const gdFields = googleapi.Field("files(" + fileFields + ")")
 
-// FULL Refresh service files
 func (fs *GDriveFS) Refresh() {
-	fs.nextRefresh = time.Now().Add(1 * time.Minute)
 
 	fileList := []*drive.File{}
 	fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
 
-	r, err := fs.client.Files.List().
+	r, err := fs.Client.Files.List().
 		OrderBy("createdTime").
 		PageSize(1000).
 		SupportsTeamDrives(true).
@@ -175,7 +105,7 @@ func (fs *GDriveFS) Refresh() {
 
 	// Rest of the pages
 	for r.NextPageToken != "" {
-		r, err = fs.client.Files.List().
+		r, err = fs.Client.Files.List().
 			OrderBy("createdTime").
 			PageToken(r.NextPageToken).
 			Fields(googleapi.Field("nextPageToken"), gdFields).
@@ -200,13 +130,13 @@ func (fs *GDriveFS) Refresh() {
 	}
 
 	// Create clean fileList
-	root := NewFileContainer(fs)
+	root := basefs.NewFileContainer(fs.BaseFS)
 	var appendFile func(gfile *drive.File)
 	appendFile = func(gfile *drive.File) {
 		for _, pID := range gfile.Parents {
 			parentFile, ok := fileMap[pID]
 			if !ok {
-				parentFile, err = fs.client.Files.Get(pID).Do()
+				parentFile, err = fs.Client.Files.Get(pID).Do()
 				if err != nil {
 					log.Println("Error fetching single file:", err)
 				}
@@ -216,12 +146,12 @@ func (fs *GDriveFS) Refresh() {
 		}
 
 		// Find existing entry
-		entry := fs.root.FindByGID(gfile.Id)
+		inode, entry := fs.Root.FindByGID(gfile.Id)
 		// Store for later add
 		if entry == nil {
-			entry = fs.root.FileEntry(gfile) // Add New and retrieve
+			inode, entry = fs.Root.FileEntry(gfile) // Add New and retrieve
 		}
-		root.AddEntry(entry)
+		root.SetEntry(inode, entry)
 		// add File
 	}
 
@@ -230,421 +160,8 @@ func (fs *GDriveFS) Refresh() {
 	}
 
 	log.Println("Refresh done, update root")
-	fs.root = root
+	fs.Root = root
 	//fs.root.children = root.children
 
-	log.Println("File count:", len(root.fileEntries))
-}
-
-///////////////////////////////
-// Fuse operations
-////////////
-
-// 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)
-	if entry == nil {
-		return fuse.ENOENT
-	}
-
-	handle := fs.createHandle()
-	handle.entry = entry
-	op.Handle = handle.ID
-
-	return // No error allow, dir open
-}
-
-// ReadDir lists files into readdirop
-func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
-	fh, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		log.Fatal("Handle does not exists")
-	}
-
-	if op.Offset == 0 { // Rebuild/rewind dir list
-		fh.entries = []fuseutil.Dirent{}
-		children := fs.root.ListByParentGID(fh.entry.GID)
-
-		for i, v := range children {
-			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)
-		}
-	}
-
-	index := int(op.Offset)
-	if index > len(fh.entries) {
-		return fuse.EINVAL
-	}
-	if index > 0 {
-		index++
-	}
-	for i := index; i < len(fh.entries); i++ {
-		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
-		if n == 0 {
-			break
-		}
-		op.BytesRead += n
-	}
-	return
-}
-
-// 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) {
-
-	// Hack to truncate file?
-
-	if op.Size != nil {
-		f := fs.root.FindByInode(op.Inode)
-
-		if *op.Size != 0 { // We only allow truncate to 0
-			return fuse.ENOSYS
-		}
-		// Delete and create another on truncate 0
-		err = fs.client.Files.Delete(f.GFile.Id).Do() // XXX: Careful on this
-		createdFile, err := fs.client.Files.Create(&drive.File{Parents: f.GFile.Parents, Name: f.GFile.Name}).Fields(fileFields).Do()
-		if err != nil {
-			return fuse.EINVAL
-		}
-		f.SetGFile(createdFile) // Set new file
-	}
-
-	return
-}
-
-//GetInodeAttributes return attributes
-func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
-
-	f := fs.root.FindByInode(op.Inode)
-	if f == nil {
-		return fuse.ENOENT
-	}
-	op.Attributes = f.Attr
-	op.AttributesExpiration = time.Now().Add(time.Minute)
-
-	return
-}
-
-// ReleaseDirHandle deletes file handle entry
-func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
-	delete(fs.fileHandles, op.Handle)
-	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) {
-
-	parentFile := fs.root.FindByInode(op.Parent) // true means transverse all
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-
-	entry := fs.root.LookupByGID(parentFile.GID, op.Name)
-
-	if entry == nil {
-		return fuse.ENOENT
-	}
-
-	// Transverse only local
-
-	now := time.Now()
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: now.Add(time.Second),
-		EntryExpiration:      now.Add(time.Second),
-	}
-	return
-}
-
-// StatFS basically allows StatFS to run
-/*func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
-	return
-}*/
-
-// ForgetInode allows to forgetInode
-func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
-	return
-}
-
-// GetXAttr special attributes
-func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
-	return
-}
-
-//////////////////////////////////////////////////////////////////////////
-// 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) {
-	f := fs.root.FindByInode(op.Inode) // might not exists
-
-	// Generate new handle
-	handle := fs.createHandle()
-	handle.entry = f
-
-	op.Handle = handle.ID
-	op.UseDirectIO = true
-
-	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) {
-	handle := fs.fileHandles[op.Handle]
-
-	localFile := handle.entry.Cache()
-	op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
-	if err == io.EOF { // fuse does not expect a EOF
-		err = nil
-	}
-
-	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) {
-
-	parentFile := fs.root.FindByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	// Only write on child folders
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	existsFile := fs.root.LookupByGID(parentFile.GID, op.Name)
-	//existsFile := parentFile.FindByName(op.Name, false)
-	if existsFile != nil {
-		return fuse.EEXIST
-	}
-
-	newGFile := &drive.File{
-		Parents: []string{parentFile.GFile.Id},
-		Name:    op.Name,
-	}
-
-	createdGFile, err := fs.client.Files.Create(newGFile).Fields(fileFields).Do()
-	if err != nil {
-		err = fuse.EINVAL
-		return
-	}
-	entry := fs.root.FileEntry(createdGFile) // New Entry added // Or Return same?
-
-	// Associate a temp file to a new handle
-	// Local copy
-	// Lock
-	handle := fs.createHandle()
-	handle.entry = entry
-	handle.uploadOnDone = true
-	//
-	op.Handle = handle.ID
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: time.Now().Add(time.Minute),
-		EntryExpiration:      time.Now().Add(time.Minute),
-	}
-	op.Mode = entry.Attr.Mode
-
-	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) {
-	handle, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-
-	localFile := handle.entry.Cache()
-	if localFile == nil {
-		return fuse.EINVAL
-	}
-
-	_, err = localFile.WriteAt(op.Data, op.Offset)
-	if err != nil {
-		err = fuse.EIO
-		return
-	}
-	handle.uploadOnDone = true
-
-	return
-}
-
-// FlushFile just returns no error, maybe upload should be handled here
-func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
-	handle, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-	if handle.entry.tempFile == nil {
-		return
-	}
-	if handle.uploadOnDone { // or if content changed basically
-		err = handle.entry.Sync()
-		if err != nil {
-			return fuse.EINVAL
-		}
-	}
-	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) {
-	handle := fs.fileHandles[op.Handle]
-
-	handle.entry.ClearCache()
-
-	delete(fs.fileHandles, op.Handle)
-
-	return
-}
-
-// Unlink remove file and remove from local cache entry
-func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
-	parentEntry := fs.root.FindByInode(op.Parent)
-	if parentEntry == nil {
-		return fuse.ENOENT
-	}
-	if parentEntry.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	fileEntry := fs.root.LookupByGID(parentEntry.GID, op.Name)
-	//fileEntry := parentEntry.FindByName(op.Name, false)
-	if fileEntry == nil {
-		return fuse.ENOATTR
-	}
-	err = fs.client.Files.Delete(fileEntry.GFile.Id).Do()
-	if err != nil {
-		return fuse.EIO
-	}
-
-	fs.root.RemoveEntry(fileEntry)
-	//parentEntry.RemoveChild(fileEntry)
-
-	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)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	// Should check existent first too
-	createdGFile, err := fs.client.Files.Create(&drive.File{
-		Parents:  []string{parentFile.GFile.Id},
-		MimeType: "application/vnd.google-apps.folder",
-		Name:     op.Name,
-	}).Fields(fileFields).Do()
-	if err != nil {
-		return fuse.ENOATTR
-	}
-	entry := fs.root.FileEntry(createdGFile)
-	//entry = parentFile.AppendGFile(fi, entry.Inode)
-	//if entry == nil {
-	//		return fuse.EINVAL
-	//	}
-
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           entry.Attr,
-		Child:                entry.Inode,
-		AttributesExpiration: time.Now().Add(time.Minute),
-		EntryExpiration:      time.Now().Add(time.Microsecond),
-	}
-
-	return
-}
-
-// RmDir fuse implementation
-func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	theFile := fs.root.LookupByGID(parentFile.GID, op.Name)
-	//theFile := parentFile.FindByName(op.Name, false)
-
-	err = fs.client.Files.Delete(theFile.GFile.Id).Do()
-	if err != nil {
-		return fuse.ENOTEMPTY
-	}
-	fs.root.RemoveEntry(theFile)
-
-	//parentFile.RemoveChild(theFile)
-
-	// Remove from entry somehow
-
-	return
-}
-
-// Rename fuse implementation
-func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
-	oldParentFile := fs.root.FindByInode(op.OldParent)
-	if oldParentFile == nil {
-		return fuse.ENOENT
-	}
-	newParentFile := fs.root.FindByInode(op.NewParent)
-	if newParentFile == nil {
-		return fuse.ENOENT
-	}
-
-	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	//oldFile := oldParentFile.FindByName(op.OldName, false)
-	oldEntry := fs.root.LookupByGID(oldParentFile.GID, op.OldName)
-
-	// Although GDrive allows duplicate names, there is some issue with inode caching
-	// So we prevent a rename to a file with same name
-	//existsFile := newParentFile.FindByName(op.NewName, false)
-	existsEntry := fs.root.LookupByGID(newParentFile.GID, op.NewName)
-	if existsEntry != nil {
-		return fuse.EEXIST
-	}
-
-	ngFile := &drive.File{
-		Name: op.NewName,
-	}
-
-	updateCall := fs.client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
-	if oldParentFile != newParentFile {
-		updateCall.RemoveParents(oldParentFile.GID)
-		updateCall.AddParents(newParentFile.GID)
-	}
-	updatedFile, err := updateCall.Do()
-
-	oldEntry.SetGFile(updatedFile)
-
-	//oldParentFile.RemoveChild(oldFile)
-	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
-
-	return
-
+	log.Println("File count:", root.Count())
 }

+ 62 - 0
internal/fs/gdrivefs/serviceimpl.go

@@ -0,0 +1,62 @@
+package gdrivefs
+
+import (
+	"io"
+	"net/http"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
+
+	"google.golang.org/api/drive/v3"
+)
+
+type gdriveService struct {
+	client *drive.Service
+}
+
+func (s *gdriveService) Truncate(file basefs.File) (basefs.File, error) {
+	err := s.client.Files.Delete(file.ID()).Do() // XXX: Careful on this
+	createdFile, err := s.client.Files.Create(&drive.File{Parents: file.Parents(), Name: file.Name()}).Fields(fileFields).Do()
+	if err != nil {
+		return nil, err
+	}
+
+	return &basefs.GFile{createdFile}, nil
+
+}
+
+func (s *gdriveService) Upload(reader io.Reader, file basefs.File) (basefs.File, error) {
+	ngFile := &drive.File{}
+	up := s.client.Files.Update(file.ID(), ngFile)
+	upFile, err := up.Media(reader).Fields(fileFields).Do()
+	if err != nil {
+		return nil, err
+	}
+
+	return &basefs.GFile{upFile}, nil
+}
+
+func (s *gdriveService) DownloadTo(w io.Writer, file basefs.File) error {
+
+	var res *http.Response
+	var err error
+	// TODO :Place this in service Download
+	gfile := file.(*basefs.GFile).File
+	// Export GDocs (Special google doc documents needs to be exported make a config somewhere for this)
+	switch gfile.MimeType { // Make this somewhat optional special case
+	case "application/vnd.google-apps.document":
+		res, err = s.client.Files.Export(gfile.Id, "text/plain").Download()
+	case "application/vnd.google-apps.spreadsheet":
+		res, err = s.client.Files.Export(gfile.Id, "text/csv").Download()
+	default:
+		res, err = s.client.Files.Get(gfile.Id).Download()
+	}
+
+	if err != nil {
+		log.Println("Error from GDrive API", err)
+		return err
+	}
+	defer res.Body.Close()
+	io.Copy(w, res.Body)
+
+	return nil
+}

+ 543 - 0
internal/fs/temp_/basefs.go

@@ -0,0 +1,543 @@
+// gdrivemount implements a google drive fuse driver
+package basefs
+
+import (
+	"io"
+	"os"
+	"sync"
+	"syscall"
+	"time"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
+	"dev.hexasoftware.com/hxs/prettylog"
+
+	"golang.org/x/net/context"
+
+	drive "google.golang.org/api/drive/v2"
+	"google.golang.org/api/googleapi"
+
+	"github.com/jacobsa/fuse"
+	"github.com/jacobsa/fuse/fuseops"
+	"github.com/jacobsa/fuse/fuseutil"
+)
+
+var (
+	log = prettylog.New("commonfs")
+)
+
+// GDriveFS
+type BaseFS struct {
+	fuseutil.NotImplementedFileSystem // Defaults
+
+	config *core.Config //core   *core.Core // Core Config instead?
+	//serviceConfig *Config
+	//client        *drive.Service
+	//root   *FileEntry // hiearchy reference
+	root *FileContainer
+
+	fileHandles map[fuseops.HandleID]*Handle
+
+	nextRefresh time.Time
+
+	handleMU *sync.Mutex
+	//fileMap map[string]
+	// Map IDS with FileEntries
+}
+
+func New(core *core.Core, driver Driver) core.DriverFS {
+
+	fs := &BaseFS{
+		config: &core.Config,
+		//serviceConfig: &Config{}, // This is on service Driver
+		fileHandles: map[fuseops.HandleID]*Handle{},
+		handleMU:    &sync.Mutex{},
+	}
+	//client := fs.initClient() // Init Oauth2 client on service Driver
+	fs.root = NewFileContainer(fs, driver)
+	fs.root.uid = core.Config.UID
+	fs.root.gid = core.Config.GID
+
+	//fs.root = rootEntry
+
+	// Temporary entry
+	entry := fs.root.FileEntry("Loading...", nil, 9999)
+	entry.Attr.Mode = os.FileMode(0)
+
+	return fs
+}
+
+// Async
+func (fs *BaseFS) Start() {
+	go func() {
+		//fs.Refresh() // First load
+
+		// Change reader loop
+		/*startPageTokenRes, err := fs.root.client.Changes.GetStartPageToken().Do()
+		if err != nil {
+			log.Println("GDrive err", err)
+		}
+		savedStartPageToken := startPageTokenRes.StartPageToken
+		for {
+			pageToken := savedStartPageToken
+			for pageToken != "" {
+				changesRes, err := fs.root.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
+				if err != nil {
+					log.Println("Err fetching changes", err)
+					break
+				}
+				//log.Println("Changes:", len(changesRes.Changes))
+				for _, c := range changesRes.Changes {
+					entry := fs.root.FindByGID(c.FileId)
+					if c.Removed {
+						if entry == nil {
+							continue
+						} else {
+							fs.root.RemoveEntry(entry)
+						}
+						continue
+					}
+
+					if entry != nil {
+						entry.SetGFile(c.File)
+					} else {
+						//Create new one
+						fs.root.FileEntry(c.File) // Creating new one
+					}
+				}
+				if changesRes.NewStartPageToken != "" {
+					savedStartPageToken = changesRes.NewStartPageToken
+				}
+				pageToken = changesRes.NextPageToken
+			}
+
+			time.Sleep(fs.config.RefreshTime)
+		}*/
+	}()
+}
+
+////////////////////////////////////////////////////////
+// TOOLS & HELPERS
+////////////////////////////////////////////////////////
+
+// COMMON
+func (fs *BaseFS) createHandle() *Handle {
+	// Lock here instead
+	fs.handleMU.Lock()
+	defer fs.handleMU.Unlock()
+
+	var handleID fuseops.HandleID
+
+	for handleID = 1; handleID < 99999; handleID++ {
+		_, ok := fs.fileHandles[handleID]
+		if !ok {
+			break
+		}
+	}
+
+	handle := &Handle{ID: handleID}
+	fs.fileHandles[handleID] = handle
+
+	return handle
+}
+
+const fileFields = googleapi.Field("id, name, size,mimeType, parents,createdTime,modifiedTime")
+const gdFields = googleapi.Field("files(" + fileFields + ")")
+
+// FULL Refresh service files
+
+///////////////////////////////
+// Fuse operations
+////////////
+
+// OpenDir return nil error allows open dir
+// COMMON for drivers
+func (fs *BaseFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
+
+	entry := fs.root.FindByInode(op.Inode)
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	handle := fs.createHandle()
+	handle.entry = entry
+	op.Handle = handle.ID
+
+	return // No error allow, dir open
+}
+
+// ReadDir lists files into readdirop
+// Common for drivers
+func (fs *BaseFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
+	fh, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		log.Fatal("Handle does not exists")
+	}
+
+	if op.Offset == 0 { // Rebuild/rewind dir list
+		fh.entries = []fuseutil.Dirent{}
+
+		children := fs.root.ListByParent(fh.entry)
+		//children := fs.root.ListByParentGID(fh.entry.GID)
+
+		for i, v := range children {
+			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)
+		}
+	}
+
+	index := int(op.Offset)
+	if index > len(fh.entries) {
+		return fuse.EINVAL
+	}
+	if index > 0 {
+		index++
+	}
+	for i := index; i < len(fh.entries); i++ {
+		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
+		if n == 0 {
+			break
+		}
+		op.BytesRead += n
+	}
+	return
+}
+
+// SPECIFIC code
+func (fs *BaseFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
+
+	// Hack to truncate file?
+
+	if op.Size != nil {
+		entry := fs.root.FindByInode(op.Inode)
+
+		if *op.Size != 0 { // We only allow truncate to 0
+			return fuse.ENOSYS
+		}
+		err = entry.Truncate()
+	}
+
+	return
+}
+
+//GetInodeAttributes return attributes
+// COMMON
+func (fs *BaseFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
+
+	f := fs.root.FindByInode(op.Inode)
+	if f == nil {
+		return fuse.ENOENT
+	}
+	op.Attributes = f.Attr
+	op.AttributesExpiration = time.Now().Add(time.Minute)
+
+	return
+}
+
+// ReleaseDirHandle deletes file handle entry
+// COMMON
+func (fs *BaseFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
+	delete(fs.fileHandles, op.Handle)
+	return
+}
+
+// LookUpInode based on Parent and Name we return a self cached inode
+// Cloud be COMMON but has specific ID
+func (fs *BaseFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
+
+	parentFile := fs.root.FindByInode(op.Parent) // true means transverse all
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+
+	entry := fs.root.LookupByParent(parentFile, op.Name)
+
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	// Transverse only local
+
+	now := time.Now()
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: now.Add(time.Second),
+		EntryExpiration:      now.Add(time.Second),
+	}
+	return
+}
+
+// ForgetInode allows to forgetInode
+// COMMON
+func (fs *BaseFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
+	return
+}
+
+// GetXAttr special attributes
+// COMMON
+func (fs *BaseFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
+	return
+}
+
+//////////////////////////////////////////////////////////////////////////
+// File OPS
+//////////////////////////////////////////////////////////////////////////
+
+// OpenFile creates a temporary handle to be handled on read or write
+// COMMON
+func (fs *BaseFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
+	f := fs.root.FindByInode(op.Inode) // might not exists
+
+	// Generate new handle
+	handle := fs.createHandle()
+	handle.entry = f
+
+	op.Handle = handle.ID
+	op.UseDirectIO = true
+
+	return
+}
+
+// COMMON but specific in cache
+func (fs *BaseFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
+	handle := fs.fileHandles[op.Handle]
+
+	localFile := handle.entry.Cache()
+	op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
+	if err == io.EOF { // fuse does not expect a EOF
+		err = nil
+	}
+
+	return
+}
+
+// CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
+// Cloud SPECIFIC
+func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
+
+	parentFile := fs.root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	// Only write on child folders
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	existsFile := fs.root.LookupByParent(parentFile, op.Name)
+	//existsFile := parentFile.FindByName(op.Name, false)
+	if existsFile != nil {
+		return fuse.EEXIST
+	}
+
+	// Parent entry/Name
+	entry, err := fs.root.CreateFile(parentFile, op.Name, false)
+	if err != nil {
+		return err
+	}
+	// Associate a temp file to a new handle
+	// Local copy
+	// Lock
+	handle := fs.createHandle()
+	handle.entry = entry
+	handle.uploadOnDone = true
+	//
+	op.Handle = handle.ID
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Minute),
+	}
+	op.Mode = entry.Attr.Mode
+
+	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
+// CLOUD SPECIFIC
+func (fs *BaseFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+
+	localFile := handle.entry.Cache()
+	if localFile == nil {
+		return fuse.EINVAL
+	}
+
+	_, err = localFile.WriteAt(op.Data, op.Offset)
+	if err != nil {
+		err = fuse.EIO
+		return
+	}
+	handle.uploadOnDone = true
+
+	return
+}
+
+// FlushFile just returns no error, maybe upload should be handled here
+// COMMON
+func (fs *BaseFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
+	handle, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+	if handle.entry.tempFile == nil {
+		return
+	}
+	if handle.uploadOnDone { // or if content changed basically
+		err = handle.entry.Sync()
+		if err != nil {
+			return fuse.EINVAL
+		}
+	}
+	return
+}
+
+// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
+// COMMON
+func (fs *BaseFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
+	handle := fs.fileHandles[op.Handle]
+
+	handle.entry.ClearCache()
+
+	delete(fs.fileHandles, op.Handle)
+
+	return
+}
+
+// Unlink remove file and remove from local cache entry
+// SPECIFIC
+func (fs *BaseFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
+	parentEntry := fs.root.FindByInode(op.Parent)
+	if parentEntry == nil {
+		return fuse.ENOENT
+	}
+	if parentEntry.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	fileEntry := fs.root.LookupByGID(parentEntry.GID, op.Name)
+	//fileEntry := parentEntry.FindByName(op.Name, false)
+	if fileEntry == nil {
+		return fuse.ENOATTR
+	}
+	return fs.root.DeleteFile(fileEntry)
+}
+
+// MkDir creates a directory on a parent dir
+func (fs *BaseFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
+
+	parentFile := fs.root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	entry, err := fs.root.CreateFile(parentFile, op.Name, true)
+	if err != nil {
+		return err
+	}
+	//entry = parentFile.AppendGFile(fi, entry.Inode)
+	//if entry == nil {
+	//		return fuse.EINVAL
+	//	}
+
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           entry.Attr,
+		Child:                entry.Inode,
+		AttributesExpiration: time.Now().Add(time.Minute),
+		EntryExpiration:      time.Now().Add(time.Microsecond),
+	}
+
+	return
+}
+
+// RmDir fuse implementation
+func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
+
+	parentFile := fs.root.FindByInode(op.Parent)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	theFile := fs.root.LookupByGID(parentFile.GID, op.Name)
+	//theFile := parentFile.FindByName(op.Name, false)
+
+	err = fs.root.DeleteFile(theFile)
+	//err = fs.client.Files.Delete(theFile.GFile.Id).Do()
+	if err != nil {
+		return fuse.ENOTEMPTY
+	}
+
+	//parentFile.RemoveChild(theFile)
+
+	// Remove from entry somehow
+
+	return
+}
+
+// Rename fuse implementation
+func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
+	oldParentFile := fs.root.FindByInode(op.OldParent)
+	if oldParentFile == nil {
+		return fuse.ENOENT
+	}
+	newParentFile := fs.root.FindByInode(op.NewParent)
+	if newParentFile == nil {
+		return fuse.ENOENT
+	}
+
+	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	//oldFile := oldParentFile.FindByName(op.OldName, false)
+	oldEntry := fs.root.LookupByGID(oldParentFile.GID, op.OldName)
+
+	// Although GDrive allows duplicate names, there is some issue with inode caching
+	// So we prevent a rename to a file with same name
+	//existsFile := newParentFile.FindByName(op.NewName, false)
+	existsEntry := fs.root.LookupByGID(newParentFile.GID, op.NewName)
+	if existsEntry != nil {
+		return fuse.EEXIST
+	}
+
+	// Rename somehow
+	ngFile := &drive.File{
+		Name: op.NewName,
+	}
+
+	updateCall := fs.root.client.Files.Update(oldEntry.GID, ngFile).Fields(fileFields)
+	if oldParentFile != newParentFile {
+		updateCall.RemoveParents(oldParentFile.GID)
+		updateCall.AddParents(newParentFile.GID)
+	}
+	updatedFile, err := updateCall.Do()
+
+	oldEntry.SetGFile(updatedFile)
+
+	//oldParentFile.RemoveChild(oldFile)
+	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
+
+	return
+
+}

+ 14 - 0
internal/fs/temp_/driver.go

@@ -0,0 +1,14 @@
+package basefs
+
+// Drive Common functions for CloudFS
+// Like: //?? seems like defailt container
+//    CreateFile
+//    MkDir
+//    Unlink
+//    GetFile
+//    UploadFile
+//    Rename
+//    Delete
+type Driver interface {
+	Upload(entry *FileEntry)
+}

+ 119 - 0
internal/fs/temp_/file_container.go

@@ -0,0 +1,119 @@
+package basefs
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"strings"
+	"sync"
+
+	"github.com/jacobsa/fuse/fuseops"
+)
+
+type FileContainer struct {
+	fileEntries map[fuseops.InodeID]*FileEntry
+	//fs          *GDriveFS
+	//client *drive.Service // Wrong should be common
+	uid uint32
+	gid uint32
+
+	inodeMU *sync.Mutex
+}
+
+// Pass config core somehow?
+func NewFileContainer(config *Config) *FileContainer {
+	fc := &FileContainer{
+		fileEntries: map[fuseops.InodeID]*FileEntry{},
+		//fs:          fs,
+		//client:  client,
+		inodeMU: &sync.Mutex{},
+		uid:     config.UID,
+		gid:     config.GID,
+	}
+	rootEntry := fc.FileEntry("", nil, fuseops.RootInodeID)
+	rootEntry.Attr.Mode = os.FileMode(0755) | os.ModeDir
+
+	return fc
+}
+
+func (fc *FileContainer) FileEntry(Name string, gfile interface{}, inodeOps ...fuseops.InodeID) *FileEntry {
+
+	fc.inodeMU.Lock()
+	defer fc.inodeMU.Unlock()
+
+	var inode fuseops.InodeID
+	if len(inodeOps) > 0 {
+		inode = inodeOps[0]
+		if fe, ok := fc.fileEntries[inode]; ok {
+			return fe
+		}
+	} else { // generate new inode
+		// Max Inode Number
+		for inode = 2; inode < 99999; inode++ {
+			_, ok := fc.fileEntries[inode]
+			if !ok {
+				break
+			}
+		}
+	}
+
+	name := ""
+	if gfile != nil {
+		name = Name //Add Name in param?
+		count := 1
+		nameParts := strings.Split(name, ".")
+		for {
+			// We find if we have a GFile in same parent with same name
+			var entry *FileEntry
+			// Check parent somehow maybe with inode
+			//for _, p := range gfile.Parents {
+			//	entry = fc.LookupByGID(p, name)
+			//	if entry != nil {
+			//		break
+			//	}
+			//}
+
+			if entry == nil { // Not found return
+				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)
+			}
+			log.Printf("Conflicting name generated new '%s' as '%s'", Name, name)
+		}
+	}
+
+	fe := &FileEntry{
+		//GFile:     gfile,
+		Inode:     inode,
+		container: fc,
+		Name:      name,
+		//children:  []*FileEntry{},
+		Attr: fuseops.InodeAttributes{
+			Uid: fc.uid,
+			Gid: fc.gid,
+		},
+	}
+	// fe.SetGFile(gfile) // Somehow get necessary information from here
+	fc.fileEntries[inode] = fe
+
+	return fe
+}
+
+func (fc *FileContainer) FindByInode(inode fuseops.InodeID) *FileEntry {
+
+	return nil // Not implemented
+}
+
+func (fc *FileContainer) ListByParent(parent *FileEntry) []*FileEntry {
+
+	return nil
+}
+
+func (fc *FileContainer) LookupByParent(parent *FileEntry, name string) *FileEntry {
+
+	return nil
+}

+ 33 - 0
internal/fs/temp_/file_entry.go

@@ -0,0 +1,33 @@
+package basefs
+
+import (
+	"errors"
+	"os"
+
+	"github.com/jacobsa/fuse/fuseops"
+)
+
+var (
+	ErrNotImplemented = errors.New("Not implemented")
+)
+
+type FileEntry struct {
+	container *FileContainer // Container reference
+	Name      string
+	Inode     fuseops.InodeID
+	Attr      fuseops.InodeAttributes
+	tempFile  *os.File
+}
+
+func (fe *FileEntry) IsDir() bool {
+	return false
+}
+
+func (fe *FileEntry) Truncate() error {
+	return ErrNotImplemented
+}
+
+func (fe *FileEntry) Cache() *os.File {
+
+	return nil
+}

+ 14 - 0
internal/fs/temp_/handle.go

@@ -0,0 +1,14 @@
+package basefs
+
+import (
+	"github.com/jacobsa/fuse/fuseops"
+	"github.com/jacobsa/fuse/fuseutil"
+)
+
+type Handle struct {
+	ID           fuseops.HandleID
+	entry        *FileEntry
+	uploadOnDone bool
+	// Handling for dir
+	entries []fuseutil.Dirent
+}

+ 2 - 1
main.go

@@ -31,6 +31,7 @@ func main() {
 
 	// More will be added later
 	core.Drivers["gdrive"] = gdrivefs.New
+	//core.Drivers["dummy"] = basefs.New
 
 	if err := parseFlags(&core.Config); err != nil {
 		log.Fatalln(err)
@@ -41,7 +42,7 @@ func main() {
 		log.Println("Err:", err)
 		return
 	}
-	// Register drivers here too
+
 	////////////////////////////////
 	// Daemon
 	/////////////////