Bladeren bron

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 jaren geleden
bovenliggende
commit
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
 	/////////////////