Explorar el Código

Changes in driver structure

luis hace 8 años
padre
commit
ad1a9d3cbf
Se han modificado 11 ficheros con 772 adiciones y 732 borrados
  1. 13 0
      SPEC.md
  2. 12 0
      cloudfs/config.go
  3. 35 25
      cloudfs/core.go
  4. 4 3
      cloudfs/driver.go
  5. 9 10
      fs/gdrivefs/client.go
  6. 8 8
      fs/gdrivefs/fileentry.go
  7. 0 658
      fs/gdrivefs/fusehandler.go
  8. 644 7
      fs/gdrivefs/gdrivefs.go
  9. 35 0
      fs/gdrivefs/gdrivefs.go.bak
  10. 11 20
      main.go
  11. 1 1
      version.go

+ 13 - 0
SPEC.md

@@ -0,0 +1,13 @@
+
+
+Package structure
+
+
+cloudfs/ core package
+
+fs/gdrivefs  
+ * Init check configuration
+ * Fetch FuseHandler
+
+
+

+ 12 - 0
cloudfs/config.go

@@ -0,0 +1,12 @@
+package cloudfs
+
+// Config struct
+type Config struct {
+	Daemonize     bool
+	CloudFSDriver string
+	VerboseLog    bool
+
+	HomeDir string
+	UID     uint32 // Mount UID
+	GID     uint32 // Mount GID
+}

+ 35 - 25
cloudfs/core.go

@@ -1,7 +1,8 @@
-package core
+package cloudfs
 
 import (
 	"context"
+	"errors"
 	"flag"
 	"fmt"
 	"log"
@@ -20,19 +21,12 @@ import (
 	"github.com/jacobsa/fuse/fuseutil"
 )
 
+// Core struct
 type Core struct {
 	Config  Config
 	Drivers map[string]DriverFactory
-}
-
-type Config struct {
-	Daemonize     bool
-	CloudFSDriver string
-	VerboseLog    bool
 
-	HomeDir string
-	UID     uint32 // Mount UID
-	GID     uint32 // Mount GID
+	CurrentFS Driver
 }
 
 // New create a New cloudmount core
@@ -40,7 +34,7 @@ func New() *Core {
 	return &Core{Drivers: map[string]DriverFactory{}}
 }
 
-func (c *Core) Init() {
+func (c *Core) Init() (err error) {
 	// TODO: friendly panics
 	usr, err := user.Current()
 	if err != nil {
@@ -51,7 +45,7 @@ func (c *Core) Init() {
 	if err != nil {
 		panic(err)
 	}
-	gid, err := strconv.Atoi(usr.Uid)
+	gid, err := strconv.Atoi(usr.Gid)
 	if err != nil {
 		panic(gid)
 	}
@@ -64,16 +58,28 @@ func (c *Core) Init() {
 		VerboseLog: false,
 		Daemonize:  false,
 	}
+	err = c.parseFlags()
+	if err != nil {
+		return err
+	}
+
+	fsFactory, ok := c.Drivers[c.Config.CloudFSDriver]
+	if !ok {
+		log.Fatal("CloudFS not supported")
+	}
 
+	c.CurrentFS = fsFactory(c) // Factory
+
+	return
 }
 
-func (c *Core) ParseFlags() {
+func (c *Core) parseFlags() (err error) {
 	var mountoptsFlag string
 
 	flag.StringVar(&c.Config.CloudFSDriver, "t", "gdrive", "which cloud service to use [gdrive]")
 	flag.BoolVar(&c.Config.Daemonize, "d", false, "Run app in background")
 	flag.BoolVar(&c.Config.VerboseLog, "v", false, "Verbose log")
-	flag.StringVar(&c.Config.HomeDir, "h", c.Config.HomeDir, "Path that holds configurations")
+	flag.StringVar(&c.Config.HomeDir, "w", c.Config.HomeDir, "Work dir, path that holds configurations")
 
 	flag.StringVar(&mountoptsFlag, "o", "", "-o [opts]  uid,gid")
 
@@ -88,7 +94,7 @@ func (c *Core) ParseFlags() {
 	if len(flag.Args()) < 1 {
 		flag.Usage()
 		//fmt.Println("Usage:\n gdrivemount [-d] [-v] MOUNTPOINT")
-		return
+		return errors.New("Missing parameter")
 	}
 	/////////////////////////////////////
 	// Parse mount opts
@@ -114,23 +120,27 @@ func (c *Core) ParseFlags() {
 		}
 		c.Config.UID = uint32(uid)
 	}
-}
 
-func (c *Core) Start() {
-
-	cloudfs, ok := c.Drivers[c.Config.CloudFSDriver]
-	if !ok {
-		log.Fatal("CloudFS not supported")
+	gidStr, ok := mountopts["gid"]
+	if ok {
+		gid, err := strconv.Atoi(gidStr)
+		if err != nil {
+			panic(err)
+		}
+		c.Config.GID = uint32(gid)
 	}
-	driveFS := cloudfs() // Constructor?
+	return
+}
 
-	// Start driveFS somehow
+func (c *Core) Mount() {
 
+	// Start driveFS somehow
+	c.CurrentFS.Start()
 	//////////////
 	// Server
 	/////////
 	ctx := context.Background()
-	server := fuseutil.NewFileSystemServer(driveFS)
+	server := fuseutil.NewFileSystemServer(c.CurrentFS)
 	mountPath := flag.Arg(0)
 
 	var err error
@@ -154,7 +164,7 @@ func (c *Core) Start() {
 			switch sig {
 			case syscall.SIGUSR1:
 				log.Println("Manually Refresh drive")
-				go driveFS.Refresh()
+				go c.CurrentFS.Refresh()
 			case syscall.SIGHUP:
 				log.Println("GC")
 				mem := runtime.MemStats{}

+ 4 - 3
cloudfs/driver.go

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

+ 9 - 10
fs/gdrivefs/client.go

@@ -16,7 +16,7 @@ import (
 	"golang.org/x/oauth2/google"
 )
 
-func (d *GDriveDriver) getClient(ctx context.Context, config *oauth2.Config) *http.Client {
+func (d *GDriveFS) getClient(ctx context.Context, config *oauth2.Config) *http.Client {
 	cacheFile, err := d.tokenCacheFile()
 	if err != nil {
 		log.Fatalf("Unable to get path to cached credential file. %v", err)
@@ -31,7 +31,7 @@ func (d *GDriveDriver) getClient(ctx context.Context, config *oauth2.Config) *ht
 
 }
 
-func (d *GDriveDriver) tokenCacheFile() (string, error) {
+func (d *GDriveFS) tokenCacheFile() (string, error) {
 	tokenCacheDir := d.core.Config.HomeDir
 
 	err := os.MkdirAll(tokenCacheDir, 0700)
@@ -40,7 +40,7 @@ func (d *GDriveDriver) tokenCacheFile() (string, error) {
 
 }
 
-func (d *GDriveDriver) getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
+func (d *GDriveFS) getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
 	authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
 
 	fmt.Printf(
@@ -64,7 +64,7 @@ type the authorization code: `, authURL)
 	return tok
 }
 
-func (d *GDriveDriver) tokenFromFile(file string) (*oauth2.Token, error) {
+func (d *GDriveFS) tokenFromFile(file string) (*oauth2.Token, error) {
 	f, err := os.Open(file)
 	if err != nil {
 		return nil, err
@@ -76,7 +76,7 @@ func (d *GDriveDriver) tokenFromFile(file string) (*oauth2.Token, error) {
 	return t, err
 }
 
-func (d *GDriveDriver) saveToken(file string, token *oauth2.Token) {
+func (d *GDriveFS) saveToken(file string, token *oauth2.Token) {
 	log.Printf("Saving credential file to: %s\n", file)
 	f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
 	if err != nil {
@@ -97,11 +97,12 @@ func (d *GDriveDriver) saveToken(file string, token *oauth2.Token) {
 	return configDir, nil
 }*/
 
-func (d *GDriveDriver) GetDriveService() *drive.Service {
+// Init driveService
+func (d *GDriveFS) initClient() {
 
 	configPath := d.core.Config.HomeDir
 
-	ctx := context.Background()
+	ctx := context.Background() // Context from GDriveFS
 
 	b, err := ioutil.ReadFile(filepath.Join(configPath, "client_secret.json"))
 	if err != nil {
@@ -114,11 +115,9 @@ func (d *GDriveDriver) GetDriveService() *drive.Service {
 	}
 
 	client := d.getClient(ctx, config)
-	srv, err := drive.New(client)
+	d.client, err = drive.New(client)
 	if err != nil {
 		log.Fatalf("Unable to retrieve drive Client: %v", err)
 	}
 
-	return srv
-
 }

+ 8 - 8
fs/gdrivefs/fileentry.go

@@ -17,7 +17,7 @@ import (
 //FileEntry entry to handle files
 type FileEntry struct {
 	//parent *FileEntry
-	fs    *FuseHandler
+	fs    *GDriveFS
 	GFile *drive.File // GDrive file
 	isDir bool        // Is dir
 	Name  string      // local name
@@ -70,8 +70,8 @@ func (fe *FileEntry) SetGFile(f *drive.File) {
 	attr.Size = uint64(f.Size)
 	//attr.Size = uint64(f.QuotaBytesUsed)
 	// Temp
-	attr.Uid = fe.core.Config.UID
-	attr.Gid = fe.core.Config.GID
+	attr.Uid = fe.fs.core.Config.UID
+	attr.Gid = fe.fs.core.Config.GID
 	attr.Crtime, _ = time.Parse(time.RFC3339, f.CreatedTime)
 	attr.Ctime = attr.Crtime // Set CTime to created, although it is change inode metadata
 	attr.Mtime, _ = time.Parse(time.RFC3339, f.ModifiedTime)
@@ -97,7 +97,7 @@ func (fe *FileEntry) Sync() (err error) {
 	fe.tempFile.Seek(0, io.SeekStart)
 
 	ngFile := &drive.File{}
-	up := fe.fs.srv.Files.Update(fe.GFile.Id, ngFile)
+	up := fe.fs.client.Files.Update(fe.GFile.Id, ngFile)
 	upFile, err := up.Media(fe.tempFile).Do()
 
 	fe.SetGFile(upFile) // update local GFile entry
@@ -127,12 +127,12 @@ func (fe *FileEntry) Cache() *os.File {
 	switch fe.GFile.MimeType { // Make this somewhat optional
 	case "application/vnd.google-apps.document":
 		log.Println("Exporting as: text/markdown")
-		res, err = fe.fs.srv.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.fs.srv.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.fs.srv.Files.Get(fe.GFile.Id).Download()
+		res, err = fe.fs.client.Files.Get(fe.GFile.Id).Download()
 	}
 
 	if err != nil {
@@ -202,7 +202,7 @@ func (fe *FileEntry) AppendGFile(f *drive.File, inode fuseops.InodeID) *FileEntr
 
 	//log.Println("Creating new file entry for name:", name, "for GFile:", f.Name)
 	// lock from find inode to fileList append
-	entry := NewFileEntry(fe.fs)
+	entry := fe.fs.NewFileEntry()
 	entry.Name = name
 	entry.SetGFile(f)
 	entry.Inode = inode

+ 0 - 658
fs/gdrivefs/fusehandler.go

@@ -1,658 +0,0 @@
-// gdrivemount implements a google drive fuse driver
-package gdrivefs
-
-import (
-	"io"
-	"os"
-	"os/user"
-	"syscall"
-	"time"
-
-	"dev.hexasoftware.com/hxs/cloudmount/cloudfs"
-	"dev.hexasoftware.com/hxs/prettylog"
-
-	"golang.org/x/net/context"
-
-	drive "google.golang.org/api/drive/v3"
-	"google.golang.org/api/googleapi"
-
-	"github.com/jacobsa/fuse"
-	"github.com/jacobsa/fuse/fuseops"
-	"github.com/jacobsa/fuse/fuseutil"
-)
-
-var (
-	log = prettylog.New("gdrivemount")
-)
-
-type fileHandle struct {
-	handleID     fuseops.HandleID
-	entry        *FileEntry
-	uploadOnDone bool
-	// Testing
-	entries []fuseutil.Dirent
-	buf     []byte
-}
-
-/*type DirEntry struct {
-	file *FileEntry
-}*/
-
-// FuseHndler handler
-type FuseHandler struct {
-	fuseutil.NotImplementedFileSystem // Defaults
-	srv                               *drive.Service
-
-	osuser *user.User
-	root   *FileEntry // hiearchy reference
-
-	fileHandles map[fuseops.HandleID]*fileHandle
-	nextRefresh time.Time
-
-	//fileMap map[string]
-	// Map IDS with FileEntries
-}
-
-func NewFuseHandler() *FuseHandler {
-
-	fs := &FuseHandler{}
-	fs.srv = GetDriveClient()
-	fs.root = &FileEntry{
-		fs: fs,
-		Attr: fuseops.InodeAttributes{
-			Mode:  os.FileMode(0755) | os.ModeDir,
-			Nlink: 1,
-			Size:  4096,
-			Uid:   core.Config.UID,
-			Gid:   core.Config.GID,
-		},
-		GFile: nil,
-		Inode: fuseops.RootInodeID,
-		Name:  "",
-		//fileMap: map[string]*FileEntry{},
-		children: []*FileEntry{},
-		isDir:    true,
-	}
-	fs.fileHandles = map[fuseops.HandleID]*fileHandle{}
-
-	// Temporary entry
-	entry := fs.root.AppendGFile(&drive.File{Id: "0", Name: "Loading..."}, 999999)
-	entry.Attr.Mode = os.FileMode(0)
-
-	fs.timedRefresh()
-
-	return fs
-}
-
-func (fs *FuseHandler) NewFileEntry() *FileEntry {
-	return &FileEntry{
-		fs:       fs,
-		children: []*FileEntry{},
-		Attr:     fuseops.InodeAttributes{},
-	}
-
-}
-
-////////////////////////////////////////////////////////
-// TOOLS & HELPERS
-////////////////////////////////////////////////////////
-
-func (fs *FuseHandler) createHandle() *fileHandle {
-	// Lock here instead
-
-	var handle fuseops.HandleID
-
-	for handle = 1; handle < 99999; handle++ {
-		_, ok := fs.fileHandles[handle]
-		if !ok {
-			break
-		}
-	}
-
-	fh := &fileHandle{handleID: handle}
-	fs.fileHandles[handle] = fh
-
-	return fh
-}
-
-// Cache somewhere?
-/*func (fs *FuseHandler) getUID() uint32 {
-	uid, _ := strconv.Atoi(fs.osuser.Uid)
-	return uint32(uid)
-}
-func (fs *FuseHandler) getGID() uint32 {
-	gid, _ := strconv.Atoi(fs.osuser.Gid)
-	return uint32(gid)
-}*/
-
-func (fs *FuseHandler) timedRefresh() {
-
-	go func() {
-		for {
-			if time.Now().After(fs.nextRefresh) {
-				fs.Refresh()
-			}
-			time.Sleep(2 * time.Minute) // 2 minutes
-		}
-	}()
-
-}
-
-// Refresh service files
-func (fs *FuseHandler) Refresh() {
-	fs.nextRefresh = time.Now().Add(1 * time.Minute)
-
-	fileList := []*drive.File{}
-	fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
-
-	gdFields := googleapi.Field("nextPageToken, files(id,name,size,quotaBytesUsed, mimeType,parents,createdTime,modifiedTime)")
-	log.Println("Loading file entries from gdrive")
-	r, err := fs.srv.Files.List().
-		OrderBy("createdTime").
-		PageSize(1000).
-		SupportsTeamDrives(true).
-		IncludeTeamDriveItems(true).
-		Fields(gdFields).
-		Do()
-	if err != nil {
-		log.Println("GDrive ERR:", err)
-		return
-	}
-	fileList = append(fileList, r.Files...)
-
-	// Rest of the pages
-	for r.NextPageToken != "" {
-		r, err = fs.srv.Files.List().
-			OrderBy("createdTime").
-			PageToken(r.NextPageToken).
-			Fields(gdFields).
-			Do()
-		if err != nil {
-			log.Println("GDrive ERR:", err)
-			return
-		}
-		fileList = append(fileList, r.Files...)
-	}
-	log.Println("Total entries:", len(fileList))
-
-	// TimeSort
-	/*log.Println("Sort by time")
-	sort.Slice(fileList, func(i, j int) bool {
-		createdTimeI, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
-		createdTimeJ, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
-		if createdTimeI.Before(createdTimeJ) {
-			return true
-		}
-		return false
-	})*/
-
-	// 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 := NewFileEntry(fs)
-
-	// Helper func to recurse
-	// Everything loaded we add to our entries
-	// Add file and its parents priorizing it parent
-	var appendFile func(df *drive.File)
-	appendFile = func(df *drive.File) {
-		for _, pID := range df.Parents {
-			parentFile, ok := fileMap[pID]
-			if !ok {
-				parentFile, err = fs.srv.Files.Get(pID).Do()
-				if err != nil {
-					panic(err)
-				}
-				fileMap[parentFile.Id] = parentFile
-			}
-			appendFile(parentFile) // Recurse
-		}
-		// Find existing entry
-		var inode fuseops.InodeID
-		entry := fs.root.FindByGID(df.Id, true)
-		if entry == nil {
-			inode = root.FindUnusedInode()
-		} else {
-			inode = entry.Inode
-		}
-
-		newEntry := root.solveAppendGFile(df, inode)     // Find right parent
-		if entry != nil && entry.GFile.Name == df.Name { // Copy name from old entry
-			newEntry.Name = entry.Name
-		}
-
-		// add File
-	}
-
-	for _, f := range fileList { // Ordered
-		appendFile(f) // Check parent first
-	}
-
-	log.Println("Refresh done, update root")
-	fs.root.children = root.children
-
-	log.Println("File count:", fs.root.Count())
-}
-
-// OpenDir return nil error allows open dir
-func (fs *FuseHandler) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
-
-	entry := fs.root.FindByInode(op.Inode, true)
-	if entry == nil {
-		return fuse.ENOENT
-	}
-
-	fh := fs.createHandle()
-	fh.entry = entry
-	op.Handle = fh.handleID
-
-	return // No error allow, dir open
-}
-
-// ReadDir lists files into readdirop
-func (fs *FuseHandler) 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{}
-
-		for i, v := range fh.entry.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])
-		//log.Println("Written:", n)
-		if n == 0 {
-			break
-		}
-		op.BytesRead += n
-	}
-	return
-}
-
-// SetInodeAttributes Not sure what attributes gdrive support we just leave this blank for now
-func (fs *FuseHandler) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
-
-	// Hack to truncate file?
-
-	if op.Size != nil {
-		f := fs.root.FindByInode(op.Inode, true)
-
-		if *op.Size != 0 { // We only allow truncate to 0
-			return fuse.ENOSYS
-		}
-		// Delete and create another on truncate 0
-		err = fs.srv.Files.Delete(f.GFile.Id).Do() // XXX: Careful on this
-		createdFile, err := fs.srv.Files.Create(&drive.File{Parents: f.GFile.Parents, Name: f.GFile.Name}).Do()
-		if err != nil {
-			return fuse.EINVAL
-		}
-		f.SetGFile(createdFile) // Set new file
-	}
-
-	return
-}
-
-//GetInodeAttributes return attributes
-func (fs *FuseHandler) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
-
-	f := fs.root.FindByInode(op.Inode, true)
-	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 *FuseHandler) 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 *FuseHandler) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent, true) // true means transverse all
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-
-	now := time.Now()
-	// Transverse only local
-	f := parentFile.FindByName(op.Name, false)
-	if f == nil {
-		return fuse.ENOENT
-	}
-
-	op.Entry = fuseops.ChildInodeEntry{
-		Attributes:           f.Attr,
-		Child:                f.Inode,
-		AttributesExpiration: now.Add(time.Second),
-		EntryExpiration:      now.Add(time.Second),
-	}
-	return
-}
-
-// StatFS basically allows StatFS to run
-/*func (fs *FuseHandler) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
-	return
-}*/
-
-// ForgetInode allows to forgetInode
-func (fs *FuseHandler) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
-	return
-}
-
-// GetXAttr special attributes
-func (fs *FuseHandler) 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 *FuseHandler) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
-	f := fs.root.FindByInode(op.Inode, true) // might not exists
-
-	// Generate new handle
-	fh := fs.createHandle()
-	fh.entry = f
-
-	op.Handle = fh.handleID
-	op.UseDirectIO = true
-
-	return
-}
-
-// ReadFile  if the first time we download the google drive file into a local temporary file
-func (fs *FuseHandler) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
-	lf := fs.fileHandles[op.Handle]
-
-	localFile := lf.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 *FuseHandler) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent, true)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	// Only write on child folders
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	existsFile := parentFile.FindByName(op.Name, false)
-	if existsFile != nil {
-		return fuse.EEXIST
-	}
-
-	// Generate ID
-	//genId, err := fs.srv.Files.GenerateIds().Count(1).Do()
-	//id := genId.Ids[0]
-	parents := []string{parentFile.GFile.Id}
-	newFile := &drive.File{
-		Parents: parents,
-		Name:    op.Name,
-	}
-	createdFile, err := fs.srv.Files.Create(newFile).Do()
-	if err != nil {
-		err = fuse.EINVAL
-		return
-	}
-
-	entry := parentFile.AppendGFile(createdFile, fs.root.FindUnusedInode()) // Add new created file
-	if entry == nil {
-		err = fuse.EINVAL
-		return
-	}
-
-	localFile := entry.Cache()
-	if localFile == nil {
-		return fuse.EINVAL
-	}
-	// Associate a temp file to a new handle
-	// Local copy
-	// Lock
-	fh := fs.createHandle()
-	fh.entry = entry
-	fh.uploadOnDone = true
-	//
-	op.Handle = fh.handleID
-	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 *FuseHandler) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
-	lf, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-
-	localFile := lf.entry.Cache()
-	if localFile == nil {
-		return fuse.EINVAL
-	}
-
-	_, err = localFile.WriteAt(op.Data, op.Offset)
-	if err != nil {
-		err = fuse.EIO
-		return
-	}
-	lf.uploadOnDone = true
-
-	return
-}
-
-// FlushFile just returns no error, maybe upload should be handled here
-func (fs *FuseHandler) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
-	lf, ok := fs.fileHandles[op.Handle]
-	if !ok {
-		return fuse.EIO
-	}
-	if lf.entry.tempFile == nil {
-		return
-	}
-	if lf.uploadOnDone { // or if content changed basically
-		err = lf.entry.Sync()
-		if err != nil {
-			return fuse.EINVAL
-		}
-	}
-
-	return
-}
-
-// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
-func (fs *FuseHandler) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
-	lf := fs.fileHandles[op.Handle]
-
-	/*if lf.uploadOnDone {
-		err = lf.entry.Sync()
-		if err != nil {
-			return fuse.EINVAL
-		}
-	}*/
-	lf.entry.ClearCache()
-	delete(fs.fileHandles, op.Handle)
-
-	return
-}
-
-// Unlink remove file and remove from local cache entry
-func (fs *FuseHandler) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
-	parentEntry := fs.root.FindByInode(op.Parent, true)
-	if parentEntry == nil {
-		return fuse.ENOENT
-	}
-	if parentEntry.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	fileEntry := parentEntry.FindByName(op.Name, false)
-	if fileEntry == nil {
-		return fuse.ENOATTR
-	}
-	err = fs.srv.Files.Delete(fileEntry.GFile.Id).Do()
-	if err != nil {
-		return fuse.EIO
-	}
-
-	parentEntry.RemoveChild(fileEntry)
-
-	return
-}
-
-// MkDir creates a directory on a parent dir
-func (fs *FuseHandler) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent, true)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	// Should check existent first too
-	fi, err := fs.srv.Files.Create(&drive.File{
-		Parents:  []string{parentFile.GFile.Id},
-		MimeType: "application/vnd.google-apps.folder",
-		Name:     op.Name,
-	}).Do()
-	if err != nil {
-		return fuse.ENOATTR
-	}
-	entry := parentFile.AppendGFile(fi, fs.root.FindUnusedInode())
-	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 *FuseHandler) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
-
-	parentFile := fs.root.FindByInode(op.Parent, true)
-	if parentFile == nil {
-		return fuse.ENOENT
-	}
-	if parentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	theFile := parentFile.FindByName(op.Name, false)
-
-	err = fs.srv.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 *FuseHandler) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
-	oldParentFile := fs.root.FindByInode(op.OldParent, true)
-	if oldParentFile == nil {
-		return fuse.ENOENT
-	}
-	newParentFile := fs.root.FindByInode(op.NewParent, true)
-	if newParentFile == nil {
-		return fuse.ENOENT
-	}
-
-	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
-		return syscall.EPERM
-	}
-
-	oldFile := oldParentFile.FindByName(op.OldName, false)
-
-	// 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)
-	if existsFile != nil {
-		return fuse.EEXIST
-	}
-
-	ngFile := &drive.File{
-		Name: op.NewName,
-	}
-
-	updateCall := fs.srv.Files.Update(oldFile.GFile.Id, ngFile)
-	if oldParentFile != newParentFile {
-		updateCall.RemoveParents(oldParentFile.GFile.Id)
-		updateCall.AddParents(newParentFile.GFile.Id)
-	}
-	updatedFile, err := updateCall.Do()
-
-	oldParentFile.RemoveChild(oldFile)
-	newParentFile.AppendGFile(updatedFile, oldFile.Inode)
-
-	return
-
-}

+ 644 - 7
fs/gdrivefs/gdrivefs.go

@@ -1,17 +1,654 @@
+// gdrivemount implements a google drive fuse driver
 package gdrivefs
 
-import "dev.hexasoftware.com/hxs/cloudmount/cloudfs"
+import (
+	"io"
+	"os"
+	"syscall"
+	"time"
 
-// Driver for gdrive
-type GDriveDriver struct {
-	core *core.Core
+	"dev.hexasoftware.com/hxs/cloudmount/cloudfs"
+	"dev.hexasoftware.com/hxs/prettylog"
+
+	"golang.org/x/net/context"
+
+	drive "google.golang.org/api/drive/v3"
+	"google.golang.org/api/googleapi"
+
+	"github.com/jacobsa/fuse"
+	"github.com/jacobsa/fuse/fuseops"
+	"github.com/jacobsa/fuse/fuseutil"
+)
+
+var (
+	log = prettylog.New("gdrivemount")
+)
+
+type fileHandle struct {
+	handleID     fuseops.HandleID
+	entry        *FileEntry
+	uploadOnDone bool
+	// Handling for dir
+	entries []fuseutil.Dirent
+}
+
+/*type DirEntry struct {
+	file *FileEntry
+}*/
+
+// FuseHndler handler
+type GDriveFS struct {
+	fuseutil.NotImplementedFileSystem // Defaults
+
+	core        *cloudfs.Core // Core Config instead?
+	client      *drive.Service
+	root        *FileEntry // hiearchy reference
+	fileHandles map[fuseops.HandleID]*fileHandle
+
+	nextRefresh time.Time
+
+	//fileMap map[string]
+	// Map IDS with FileEntries
+}
+
+func New(core *cloudfs.Core) cloudfs.Driver {
+
+	fs := &GDriveFS{
+		core:        core,
+		fileHandles: map[fuseops.HandleID]*fileHandle{},
+	}
+	fs.initClient() // Init Oauth2 client
+
+	rootEntry := fs.NewFileEntry()
+	rootEntry.Attr = fuseops.InodeAttributes{
+		Mode: os.FileMode(0755) | os.ModeDir,
+		Uid:  core.Config.UID,
+		Gid:  core.Config.GID,
+	}
+	rootEntry.Inode = fuseops.RootInodeID
+	rootEntry.isDir = true
+
+	fs.root = rootEntry
+
+	// Temporary entry
+	entry := fs.root.AppendGFile(&drive.File{Id: "0", Name: "Loading..."}, 999999)
+	entry.Attr.Mode = os.FileMode(0)
+
+	return fs
+}
+
+func (fs *GDriveFS) Start() {
+	fs.timedRefresh()
+}
+
+func (fs *GDriveFS) NewFileEntry() *FileEntry {
+	return &FileEntry{
+		fs:       fs,
+		children: []*FileEntry{},
+		Attr:     fuseops.InodeAttributes{},
+	}
+}
+
+////////////////////////////////////////////////////////
+// TOOLS & HELPERS
+////////////////////////////////////////////////////////
+
+func (fs *GDriveFS) createHandle() *fileHandle {
+	// Lock here instead
+
+	var handle fuseops.HandleID
+
+	for handle = 1; handle < 99999; handle++ {
+		_, ok := fs.fileHandles[handle]
+		if !ok {
+			break
+		}
+	}
+
+	fh := &fileHandle{handleID: handle}
+	fs.fileHandles[handle] = fh
+
+	return fh
+}
+
+// Cache somewhere?
+/*func (fs *GDriveFS) getUID() uint32 {
+	uid, _ := strconv.Atoi(fs.osuser.Uid)
+	return uint32(uid)
+}
+func (fs *GDriveFS) getGID() uint32 {
+	gid, _ := strconv.Atoi(fs.osuser.Gid)
+	return uint32(gid)
+}*/
+
+func (fs *GDriveFS) timedRefresh() {
+
+	go func() {
+		for {
+			if time.Now().After(fs.nextRefresh) {
+				fs.Refresh()
+			}
+			time.Sleep(2 * time.Minute) // 2 minutes
+		}
+	}()
+
+}
+
+// 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
+
+	gdFields := googleapi.Field("nextPageToken, files(id,name,size,quotaBytesUsed, mimeType,parents,createdTime,modifiedTime)")
+	log.Println("Loading file entries from gdrive")
+	r, err := fs.client.Files.List().
+		OrderBy("createdTime").
+		PageSize(1000).
+		SupportsTeamDrives(true).
+		IncludeTeamDriveItems(true).
+		Fields(gdFields).
+		Do()
+	if err != nil {
+		log.Println("GDrive ERR:", err)
+		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(gdFields).
+			Do()
+		if err != nil {
+			log.Println("GDrive ERR:", err)
+			return
+		}
+		fileList = append(fileList, r.Files...)
+	}
+	log.Println("Total entries:", len(fileList))
+
+	// TimeSort
+	/*log.Println("Sort by time")
+	sort.Slice(fileList, func(i, j int) bool {
+		createdTimeI, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
+		createdTimeJ, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
+		if createdTimeI.Before(createdTimeJ) {
+			return true
+		}
+		return false
+	})*/
+
+	// 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 := fs.NewFileEntry()
+
+	// Helper func to recurse
+	// Everything loaded we add to our entries
+	// Add file and its parents priorizing it parent
+	var appendFile func(df *drive.File)
+	appendFile = func(df *drive.File) {
+		for _, pID := range df.Parents {
+			parentFile, ok := fileMap[pID]
+			if !ok {
+				parentFile, err = fs.client.Files.Get(pID).Do()
+				if err != nil {
+					panic(err)
+				}
+				fileMap[parentFile.Id] = parentFile
+			}
+			appendFile(parentFile) // Recurse
+		}
+		// Find existing entry
+		var inode fuseops.InodeID
+		entry := fs.root.FindByGID(df.Id, true)
+		if entry == nil {
+			inode = root.FindUnusedInode()
+		} else {
+			inode = entry.Inode
+		}
+
+		newEntry := root.solveAppendGFile(df, inode)     // Find right parent
+		if entry != nil && entry.GFile.Name == df.Name { // Copy name from old entry
+			newEntry.Name = entry.Name
+		}
+
+		// add File
+	}
+
+	for _, f := range fileList { // Ordered
+		appendFile(f) // Check parent first
+	}
+
+	log.Println("Refresh done, update root")
+	fs.root.children = root.children
+
+	log.Println("File count:", fs.root.Count())
+}
+
+// OpenDir return nil error allows open dir
+func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
+
+	entry := fs.root.FindByInode(op.Inode, true)
+	if entry == nil {
+		return fuse.ENOENT
+	}
+
+	fh := fs.createHandle()
+	fh.entry = entry
+	op.Handle = fh.handleID
+
+	return // No error allow, dir open
 }
 
-func (d *GDriveDriver) Init(core *core.Core) {
-	d.core = core
+// 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{}
 
+		for i, v := range fh.entry.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])
+		//log.Println("Written:", n)
+		if n == 0 {
+			break
+		}
+		op.BytesRead += n
+	}
+	return
 }
 
-func (d *GDriveDriver) Start() {
+// 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, true)
+
+		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}).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, true)
+	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) // true means transverse all
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+
+	now := time.Now()
+	// Transverse only local
+	f := parentFile.FindByName(op.Name, false)
+	if f == nil {
+		return fuse.ENOENT
+	}
+
+	op.Entry = fuseops.ChildInodeEntry{
+		Attributes:           f.Attr,
+		Child:                f.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, true) // might not exists
+
+	// Generate new handle
+	fh := fs.createHandle()
+	fh.entry = f
+
+	op.Handle = fh.handleID
+	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) {
+	lf := fs.fileHandles[op.Handle]
+
+	localFile := lf.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, true)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	// Only write on child folders
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	existsFile := parentFile.FindByName(op.Name, false)
+	if existsFile != nil {
+		return fuse.EEXIST
+	}
+
+	// Generate ID
+	//genId, err := fs.client.Files.GenerateIds().Count(1).Do()
+	//id := genId.Ids[0]
+	parents := []string{parentFile.GFile.Id}
+	newFile := &drive.File{
+		Parents: parents,
+		Name:    op.Name,
+	}
+	createdFile, err := fs.client.Files.Create(newFile).Do()
+	if err != nil {
+		err = fuse.EINVAL
+		return
+	}
+
+	entry := parentFile.AppendGFile(createdFile, fs.root.FindUnusedInode()) // Add new created file
+	if entry == nil {
+		err = fuse.EINVAL
+		return
+	}
+
+	localFile := entry.Cache()
+	if localFile == nil {
+		return fuse.EINVAL
+	}
+	// Associate a temp file to a new handle
+	// Local copy
+	// Lock
+	fh := fs.createHandle()
+	fh.entry = entry
+	fh.uploadOnDone = true
+	//
+	op.Handle = fh.handleID
+	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) {
+	lf, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+
+	localFile := lf.entry.Cache()
+	if localFile == nil {
+		return fuse.EINVAL
+	}
+
+	_, err = localFile.WriteAt(op.Data, op.Offset)
+	if err != nil {
+		err = fuse.EIO
+		return
+	}
+	lf.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) {
+	lf, ok := fs.fileHandles[op.Handle]
+	if !ok {
+		return fuse.EIO
+	}
+	if lf.entry.tempFile == nil {
+		return
+	}
+	if lf.uploadOnDone { // or if content changed basically
+		err = lf.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) {
+	lf := fs.fileHandles[op.Handle]
+
+	/*if lf.uploadOnDone {
+		err = lf.entry.Sync()
+		if err != nil {
+			return fuse.EINVAL
+		}
+	}*/
+	lf.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, true)
+	if parentEntry == nil {
+		return fuse.ENOENT
+	}
+	if parentEntry.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	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
+	}
+
+	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, true)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	// Should check existent first too
+	fi, err := fs.client.Files.Create(&drive.File{
+		Parents:  []string{parentFile.GFile.Id},
+		MimeType: "application/vnd.google-apps.folder",
+		Name:     op.Name,
+	}).Do()
+	if err != nil {
+		return fuse.ENOATTR
+	}
+	entry := parentFile.AppendGFile(fi, fs.root.FindUnusedInode())
+	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, true)
+	if parentFile == nil {
+		return fuse.ENOENT
+	}
+	if parentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	theFile := parentFile.FindByName(op.Name, false)
+
+	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, true)
+	if oldParentFile == nil {
+		return fuse.ENOENT
+	}
+	newParentFile := fs.root.FindByInode(op.NewParent, true)
+	if newParentFile == nil {
+		return fuse.ENOENT
+	}
+
+	if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
+		return syscall.EPERM
+	}
+
+	oldFile := oldParentFile.FindByName(op.OldName, false)
+
+	// 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)
+	if existsFile != nil {
+		return fuse.EEXIST
+	}
+
+	ngFile := &drive.File{
+		Name: op.NewName,
+	}
+
+	updateCall := fs.client.Files.Update(oldFile.GFile.Id, ngFile)
+	if oldParentFile != newParentFile {
+		updateCall.RemoveParents(oldParentFile.GFile.Id)
+		updateCall.AddParents(newParentFile.GFile.Id)
+	}
+	updatedFile, err := updateCall.Do()
+
+	oldParentFile.RemoveChild(oldFile)
+	newParentFile.AppendGFile(updatedFile, oldFile.Inode)
+
+	return
 
 }

+ 35 - 0
fs/gdrivefs/gdrivefs.go.bak

@@ -0,0 +1,35 @@
+package gdrivefs
+
+import (
+	"dev.hexasoftware.com/hxs/cloudmount/cloudfs"
+	"github.com/jacobsa/fuse/fuseutil"
+	drive "google.golang.org/api/drive/v3"
+)
+
+// Driver for gdrive
+type GDriveDriver struct {
+	core *cloudfs.Core
+
+	fuseHandler *FuseHandler
+	driveClient *drive.Service
+}
+
+func New() cloudfs.Driver {
+	return &GDriveDriver{}
+
+}
+
+func (d *GDriveDriver) Init(core *cloudfs.Core) {
+	d.core = core
+	d.driveClient = d.GetDriveService()
+}
+
+func (d *GDriveDriver) Refresh() {
+
+}
+
+func (d *GDriveDriver) FuseHandler() fuseutil.FileSystem {
+
+	return NewFuseHandler(d)
+
+}

+ 11 - 20
main.go

@@ -10,11 +10,10 @@ import (
 
 	"os/exec"
 
-	"dev.hexasoftware.com/hxs/cloudmount/cloudfs"
-
 	"dev.hexasoftware.com/hxs/prettylog"
 
-	_ "dev.hexasoftware.com/hxs/cloudmount/fs/gdrivefs"
+	"dev.hexasoftware.com/hxs/cloudmount/cloudfs"
+	"dev.hexasoftware.com/hxs/cloudmount/fs/gdrivefs"
 	//_ "github.com/icattlecoder/godaemon" // No reason
 )
 
@@ -29,29 +28,19 @@ func main() {
 	// getClient
 	fmt.Printf("%s-%s\n", Name, Version)
 
-	core := core.New()
-
+	core := cloudfs.New()
 	core.Drivers["gdrive"] = gdrivefs.New
 
-	core.Init()
-	// Register drivers here too
-	core.ParseFlags()
-
-	core.Start()
-
-	///////////////////////////////
-	// cloud drive Type
-	/////////////////
-	/*f, ok := core.Drivers[clouddriveFlag] // there can be some interaction before daemon
-	if !ok {
-		log.Fatal("FileSystem not supported")
+	err := core.Init()
+	if err != nil {
+		log.Println("Err:", err)
+		return
 	}
-	driveFS := f()*/
-
+	// Register drivers here too
 	////////////////////////////////
 	// Daemon
 	/////////////////
-	if daemonizeFlag {
+	if core.Config.Daemonize {
 		subArgs := []string{}
 		for _, arg := range os.Args[1:] {
 			if arg == "-d" { // ignore daemon flag
@@ -67,4 +56,6 @@ func main() {
 		return
 	}
 
+	core.Mount()
+
 }

+ 1 - 1
version.go

@@ -2,5 +2,5 @@ package main
 
 const (
   //Version contains version of the package
-  Version = "0.1 - built: 2017-07-07 10:30:51 UTC"
+  Version = "0.1 - built: 2017-07-09 20:47:15 UTC"
 )