Ver código fonte

Cleaning and bugfixing

Cleaned dropbox/gdrive fs moved some functions to common
Added flag safemode to options,
Removed permission to write in root
dropbox seems fully implemented with change polling
luis 7 anos atrás
pai
commit
f01e8fbc4d

+ 4 - 0
SPEC.md

@@ -1,5 +1,9 @@
 
 
+
+Fuse Interface -> File container -> Service (produces File)
+
+
 Package structure
 
 

+ 22 - 0
TODO.md

@@ -0,0 +1,22 @@
+#### TODO:   
+* Safemode flag not needed i supose 
+* Add verbosity levels (sometimes just want to log the driver and not fuse)
+* Create test cases
+* Create and reference dropbox oauth doc
+* Remove default gdrive and determine fs by arg[0] when possible
+	* cloudmount.gdrive will mount gdrive
+	* cloudmount.dropbox ..
+
+#### Done:   
+* move client from fs's to service.go
+* Sanitize error on basefs, file_container produces err, basefs produces fuse.E..
+
+
+#### Ideas:
+Sub mounting:
+
+Original:  
+cloudmount -t gdrive source.yaml destfolder
+
+Idea:   
+cloudmount -t gdrive gdrive.yaml/My\ Drive destfolder

+ 4 - 1
flags.go

@@ -21,7 +21,7 @@ func parseFlags(config *core.Config) (err error) {
 	flag.StringVar(&config.HomeDir, "w", config.HomeDir, "Work dir, path that holds configurations")
 	flag.DurationVar(&config.RefreshTime, "r", config.RefreshTime, "Timed cloud synchronization interval [if applied]")
 
-	flag.StringVar(&mountoptsFlag, "o", "", "uid,gid ex: -o uid=1000,gid=0 ")
+	flag.StringVar(&mountoptsFlag, "o", "", "uid,gid,safemode ex: -o uid=1000,gid=0")
 
 	flag.Usage = func() {
 		fmt.Fprintf(os.Stderr, "\n")
@@ -78,5 +78,8 @@ func parseFlags(config *core.Config) (err error) {
 		}
 		config.GID = uint32(gid)
 	}
+	if mountopts["safemode"] == "true" {
+		config.Safemode = true
+	}
 	return
 }

+ 1 - 0
internal/core/config.go

@@ -13,6 +13,7 @@ type Config struct {
 	GID         uint32 // Mount GID
 	Target      string // should be a folder
 	Source      string
+	Safemode    bool
 
 	// Driver specific params:
 	Param map[string]interface{}

+ 4 - 2
internal/core/core.go

@@ -57,8 +57,10 @@ func New() *Core {
 			RefreshTime: 5 * time.Second,
 			HomeDir:     filepath.Join(usr.HomeDir, ".cloudmount"),
 			Source:      filepath.Join(usr.HomeDir, ".cloudmount", "gdrive.yaml"),
-			UID:         uint32(uid),
-			GID:         uint32(gid),
+
+			Safemode: false,
+			UID:      uint32(uid),
+			GID:      uint32(gid),
 		},
 	}
 

+ 1 - 1
internal/core/util.go

@@ -9,7 +9,7 @@ import (
 	"github.com/go-yaml/yaml"
 )
 
-// Some utils
+// ParseConfig, reads yaml or json file into a struct
 func ParseConfig(srcfile string, out interface{}) (err error) {
 	if srcfile == "" {
 		return

+ 84 - 28
internal/fs/basefs/basefs.go

@@ -4,6 +4,8 @@ package basefs
 import (
 	"errors"
 	"io"
+	"io/ioutil"
+	glog "log"
 	"math"
 	"os"
 	"sync"
@@ -25,9 +27,11 @@ import (
 const maxInodes = math.MaxUint64
 
 var (
-	log = prettylog.New("basefs")
+	log = glog.New(ioutil.Discard, "", 0)
 	// ErrNotImplemented basic Not implemented error
 	ErrNotImplemented = errors.New("Not implemented")
+	// ErrPermission permission denied error
+	ErrPermission = errors.New("Permission denied")
 )
 
 type handle struct {
@@ -51,6 +55,10 @@ type BaseFS struct {
 
 // New Creates a new BaseFS with config based on core
 func New(core *core.Core) *BaseFS {
+	if core.Config.VerboseLog {
+		log = prettylog.New("basefs")
+
+	}
 
 	fs := &BaseFS{
 		Config:      &core.Config,
@@ -69,21 +77,57 @@ func New(core *core.Core) *BaseFS {
 	return fs
 }
 
+// Start BaseFS service with loop for changes
+func (fs *BaseFS) Start() {
+	// Fill root container and do changes
+	go func() {
+		fs.Refresh()
+		for {
+			fs.CheckForChanges()
+			time.Sleep(fs.Config.RefreshTime)
+		}
+	}()
+}
+
 // Refresh should be renamed to Load or something
 func (fs *BaseFS) Refresh() {
 	// Try
 	files, err := fs.Service.ListAll()
-	if err == nil { // Do something repeat maybe?
-
+	if err != nil { // Repeat refresh maybe?
 	}
+
+	log.Println("Files loaded:", len(files))
 	root := NewFileContainer(fs)
 	for _, file := range files {
 		root.FileEntry(file) // Try to find in previous root
 	}
-	log.Println("File count:", root.Count())
+	log.Println("Files processed")
 	fs.Root = root
 }
 
+// CheckForChanges polling
+func (fs *BaseFS) CheckForChanges() {
+	changes, err := fs.Service.Changes()
+	if err != nil {
+		return
+	}
+	for _, c := range changes {
+		entry := fs.Root.FindByID(c.ID)
+		if c.Remove {
+			if entry != nil {
+				fs.Root.RemoveEntry(entry)
+			}
+			continue
+		}
+		if entry != nil {
+			entry.SetFile(c.File, fs.Config.UID, fs.Config.GID)
+		} else {
+			//Create new one
+			fs.Root.FileEntry(c.File) // Creating new one
+		}
+	}
+}
+
 ////////////////////////////////////////////////////////
 // TOOLS & HELPERS
 ////////////////////////////////////////////////////////
@@ -141,9 +185,9 @@ func (fs *BaseFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error
 	}
 
 	if op.Offset == 0 { // Rebuild/rewind dir list
+
 		fh.entries = []fuseutil.Dirent{}
 		children := fs.Root.ListByParent(fh.entry)
-
 		for i, v := range children {
 			fusetype := fuseutil.DT_File
 			if v.IsDir() {
@@ -167,6 +211,7 @@ func (fs *BaseFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error
 	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 {
@@ -302,7 +347,7 @@ func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err
 		return fuse.ENOENT
 	}
 	// Only write on child folders
-	if parentFile == fs.Root.FileEntries[fuseops.RootInodeID] {
+	if fs.Config.Safemode && parentFile == fs.Root.FileEntries[fuseops.RootInodeID] {
 		return syscall.EPERM
 	}
 
@@ -315,7 +360,7 @@ func (fs *BaseFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err
 	// Parent entry/Name
 	entry, err := fs.Root.CreateFile(parentFile, op.Name, false)
 	if err != nil {
-		return err
+		return fuseErr(err)
 	}
 	// Associate a temp file to a new handle
 	// Local copy
@@ -372,7 +417,7 @@ func (fs *BaseFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err e
 	if handle.uploadOnDone { // or if content changed basically
 		err = fs.Root.Sync(handle.entry)
 		if err != nil {
-			return fuse.EINVAL
+			return fuseErr(err)
 		}
 	}
 	return
@@ -393,7 +438,7 @@ func (fs *BaseFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFile
 // 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 {
+	if fs.Config.Safemode && op.Parent == fuseops.RootInodeID {
 		return syscall.EPERM
 	}
 	parentEntry := fs.Root.FindByInode(op.Parent)
@@ -406,12 +451,14 @@ func (fs *BaseFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error)
 	if fileEntry == nil {
 		return fuse.ENOATTR
 	}
-	return fs.Root.DeleteFile(fileEntry)
+	err = fs.Root.DeleteFile(fileEntry)
+
+	return fuseErr(err)
 }
 
 // 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 {
+	if fs.Config.Safemode && op.Parent == fuseops.RootInodeID {
 		return syscall.EPERM
 	}
 
@@ -422,7 +469,7 @@ func (fs *BaseFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
 
 	entry, err := fs.Root.CreateFile(parentFile, op.Name, true)
 	if err != nil {
-		return err
+		return fuseErr(err)
 	}
 
 	op.Entry = fuseops.ChildInodeEntry{
@@ -437,7 +484,7 @@ func (fs *BaseFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
 
 // RmDir fuse implementation
 func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
-	if op.Parent == fuseops.RootInodeID {
+	if fs.Config.Safemode && op.Parent == fuseops.RootInodeID {
 		return syscall.EPERM
 	}
 
@@ -450,7 +497,7 @@ func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
 
 	err = fs.Root.DeleteFile(theFile)
 	if err != nil {
-		return fuse.ENOTEMPTY
+		return fuseErr(err)
 	}
 
 	return
@@ -458,43 +505,52 @@ func (fs *BaseFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
 
 // Rename fuse implementation
 func (fs *BaseFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
-	if op.OldParent == fuseops.RootInodeID || op.NewParent == fuseops.RootInodeID {
+
+	if fs.Config.Safemode && (op.OldParent == fuseops.RootInodeID || op.NewParent == fuseops.RootInodeID) {
 		return syscall.EPERM
 	}
-	oldParentFile := fs.Root.FindByInode(op.OldParent)
-	if oldParentFile == nil {
+	oldParentEntry := fs.Root.FindByInode(op.OldParent)
+	if oldParentEntry == nil {
 		return fuse.ENOENT
 	}
-	newParentFile := fs.Root.FindByInode(op.NewParent)
-	if newParentFile == nil {
+	newParentEntry := fs.Root.FindByInode(op.NewParent)
+	if newParentEntry == nil {
 		return fuse.ENOENT
 	}
 
 	//oldFile := oldParentFile.FindByName(op.OldName, false)
-	oldEntry := fs.Root.Lookup(oldParentFile, op.OldName)
+	oldEntry := fs.Root.Lookup(oldParentEntry, 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)
+	existsEntry := fs.Root.Lookup(newParentEntry, op.NewName)
 	if existsEntry != nil {
 		return fuse.EEXIST
 	}
 
-	nFile, err := fs.Service.Move(oldEntry.File, newParentFile.File, op.NewName)
+	nFile, err := fs.Service.Move(oldEntry.File, newParentEntry.File, op.NewName)
 	if err != nil {
-		return fuse.EINVAL
+		return fuseErr(err)
 	}
 
-	//oldEntry.SetFile(nFile, fs.Config.UID, fs.Config.GID)
-
 	// Why remove and add instead of setting file, is just in case we have an existing name FileEntry solves the name adding duplicates helpers
 	fs.Root.RemoveEntry(oldEntry)
 	fs.Root.FileEntry(nFile, oldEntry.Inode) // Use this same inode
 
-	//oldParentFile.RemoveChild(oldFile)
-	//newParentFile.AppendGFile(updatedFile, oldFile.Inode)
-
 	return
 
 }
+
+func fuseErr(err error) error {
+	switch err {
+	case ErrPermission:
+		return syscall.EPERM
+	case ErrNotImplemented:
+		return fuse.ENOSYS
+	case nil:
+		return nil
+	default:
+		return fuse.EINVAL
+	}
+}

+ 7 - 0
internal/fs/basefs/change.go

@@ -0,0 +1,7 @@
+package basefs
+
+type Change struct {
+	ID     string
+	File   *File
+	Remove bool
+}

+ 0 - 6
internal/fs/basefs/changes.go

@@ -1,6 +0,0 @@
-package basefs
-
-type Change struct {
-	File    *File
-	deleted bool
-}

+ 5 - 8
internal/fs/basefs/file.go

@@ -7,13 +7,6 @@ import (
 
 // This could be a struct
 // And service would be creating these
-/*type File interface {
-	ID() string
-	Name() string
-	Attr() fuseops.InodeAttributes
-	Parents() []string
-	HasParent(file File) bool
-}*/
 type File struct {
 	ID   string
 	Name string
@@ -28,8 +21,12 @@ type File struct {
 }
 
 func (f *File) HasParent(parent *File) bool {
+	parentID := ""
+	if parent != nil {
+		parentID = parent.ID
+	}
 	for _, p := range f.Parents {
-		if p == parent.ID {
+		if p == parentID {
 			return true
 		}
 	}

+ 21 - 8
internal/fs/basefs/file_container.go

@@ -9,7 +9,6 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/jacobsa/fuse"
 	"github.com/jacobsa/fuse/fuseops"
 )
 
@@ -35,6 +34,8 @@ func NewFileContainer(fs *BaseFS) *FileContainer {
 	}
 	rootEntry := fc.FileEntry(nil, fuseops.RootInodeID)
 	rootEntry.Attr.Mode = os.FileMode(0755) | os.ModeDir
+	rootEntry.Attr.Uid = fs.Config.UID
+	rootEntry.Attr.Gid = fs.Config.GID
 
 	return fc
 }
@@ -47,14 +48,18 @@ func (fc *FileContainer) FindByInode(inode fuseops.InodeID) *FileEntry {
 	return fc.FileEntries[inode]
 }
 func (fc *FileContainer) FindByID(id string) *FileEntry {
+	log.Println("Searching for :", id)
 	for _, v := range fc.FileEntries {
 		if v.File == nil && id == "" {
+			log.Println("Found cause file is nil and id '' inode:", v.Inode)
 			return v
 		}
 		if v.File != nil && v.File.ID == id {
+			log.Println("Found by id")
 			return v
 		}
 	}
+	log.Println("Not found")
 	return nil
 }
 
@@ -87,13 +92,15 @@ func (fc *FileContainer) CreateFile(parentFile *FileEntry, name string, isDir bo
 	}
 	entry := fc.FileEntry(createdFile) // New Entry added // Or Return same?
 
+	//fc.Truncate(entry) // Dropbox dont have a way to upload?
+
 	return entry, nil
 }
 
 func (fc *FileContainer) DeleteFile(entry *FileEntry) error {
 	err := fc.fs.Service.Delete(entry.File)
 	if err != nil {
-		return fuse.EINVAL
+		return err
 	}
 	fc.RemoveEntry(entry)
 	return nil
@@ -123,6 +130,9 @@ func (fc *FileContainer) FileEntry(file *File, inodeOps ...fuseops.InodeID) *Fil
 		}
 	}
 
+	/////////////////////////////////////////////////////////////
+	// Important some file systems might support insane chars
+	////////////////////////////////////
 	// Name solver
 	name := ""
 	if file != nil {
@@ -151,6 +161,14 @@ func (fc *FileContainer) FileEntry(file *File, inodeOps ...fuseops.InodeID) *Fil
 			log.Printf("Conflicting name generated new '%s' as '%s'", file.Name, name)
 		}
 	}
+	/////////////////////////////////
+	// Filename sanitizer?, There might be other unsupported char
+	////
+	if strings.Contains(name, "/") { // Something to inform user
+		newName := strings.Replace(name, "/", "_", -1)
+		log.Println("Filename contains invalid chars, sanitizing: '%s'-'%s'", name, newName)
+		name = newName
+	}
 
 	fe := &FileEntry{
 		Inode: inode,
@@ -235,17 +253,12 @@ func (fc *FileContainer) Cache(fe *FileEntry) *fileWrapper {
 // Truncate truncates localFile to 0 bytes
 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
-	//	}
 	localFile, err := ioutil.TempFile(os.TempDir(), "gdfs") // TODO: const this elsewhere
 	if err != nil {
 		return err
 	}
 	fe.tempFile = &fileWrapper{localFile}
-	//fc.Sync(fe) // Basically upload empty file
+	//fc.Sync(fe) // Basically upload empty file??
 
 	return
 }

+ 2 - 0
internal/fs/basefs/file_wrapper.go

@@ -4,6 +4,8 @@ package basefs
 
 import "os"
 
+// osfileWrapper
+
 type fileWrapper struct {
 	*os.File
 }

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

@@ -2,7 +2,9 @@ package basefs
 
 import "io"
 
+// Service interface
 type Service interface {
+	Changes() ([]*Change, error)
 	ListAll() ([]*File, error)
 	Create(parent *File, name string, isDir bool) (*File, error)
 	//Truncate(file *File) (*File, error)

+ 0 - 61
internal/fs/dropboxfs/client.go

@@ -1,61 +0,0 @@
-package dropboxfs
-
-import (
-	"fmt"
-
-	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
-
-	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
-	"golang.org/x/oauth2"
-)
-
-func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
-	authURL := config.AuthCodeURL("state-token")
-
-	fmt.Printf(
-		`Go to the following link in your browser: 
-----------------------------------------------------------------------------------------------
-%v
-----------------------------------------------------------------------------------------------
-
-type the authorization code: `, authURL)
-
-	var code string
-	if _, err := fmt.Scan(&code); err != nil {
-		log.Fatalf("Unable to read authorization code %v", err)
-	}
-
-	tok, err := config.Exchange(oauth2.NoContext, code)
-	if err != nil {
-		log.Fatalf("Unable to retrieve token from web: %v", err)
-	}
-
-	return tok
-}
-
-func (fs *DropboxFS) initClient() dropbox.Config {
-
-	err := core.ParseConfig(fs.Config.Source, fs.serviceConfig)
-	if err != nil {
-		log.Fatalf("Unable to read <source>: %v", err)
-	}
-
-	config := &oauth2.Config{
-		ClientID:     fs.serviceConfig.ClientSecret.ClientID,
-		ClientSecret: fs.serviceConfig.ClientSecret.ClientSecret,
-		RedirectURL:  "",
-		Scopes:       []string{},
-		Endpoint: oauth2.Endpoint{
-			AuthURL:  "https://www.dropbox.com/1/oauth2/authorize",
-			TokenURL: "https://api.dropbox.com/1/oauth2/token",
-		},
-	}
-	if fs.serviceConfig.Auth == nil {
-		tok := getTokenFromWeb(config)
-		fs.serviceConfig.Auth = tok
-		core.SaveConfig(fs.Config.Source, fs.serviceConfig)
-	}
-	dbconfig := dropbox.Config{Token: fs.serviceConfig.Auth.AccessToken}
-
-	return dbconfig
-}

+ 5 - 3
internal/fs/dropboxfs/config.go

@@ -2,12 +2,14 @@ package dropboxfs
 
 import "golang.org/x/oauth2"
 
-//Silimar to gdrive
+//Config Configuration
 type Config struct {
 	ClientSecret struct {
 		ClientID     string `json:"client_id" yaml:"client_id"`
 		ClientSecret string `json:"client_secret" yaml:"client_secret"`
 	} `json:"client_secret" yaml:"client_secret"`
-
-	Auth *oauth2.Token `json:"auth" yaml:"auth"`
+	Auth    *oauth2.Token `json:"auth" yaml:"auth"`
+	Options struct {
+		Safemode bool
+	}
 }

+ 9 - 27
internal/fs/dropboxfs/dropboxfs.go

@@ -1,7 +1,8 @@
 package dropboxfs
 
 import (
-	"time"
+	"io/ioutil"
+	glog "log"
 
 	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
 	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
@@ -9,35 +10,16 @@ import (
 )
 
 var (
-	log = prettylog.New("dropboxfs")
+	log = glog.New(ioutil.Discard, "", 0)
 )
 
-type DropboxFS struct {
-	*basefs.BaseFS
-	serviceConfig *Config
-	nextRefresh   time.Time
-}
-
+// New Create basefs with Dropbox service
 func New(core *core.Core) core.DriverFS {
-
-	fs := &DropboxFS{basefs.New(core), &Config{}, time.Now()}
-	client := fs.initClient() // Init Oauth2 client
-	//client.Verbose = true
-
-	// Set necesary service
-	fs.BaseFS.Service = &Service{client}
+	if core.Config.VerboseLog {
+		log = prettylog.New("dropboxfs")
+	}
+	fs := basefs.New(core)
+	fs.Service = NewService(&core.Config) // DropBoxService
 
 	return fs
 }
-
-func (fs *DropboxFS) Start() {
-	Service := fs.Service.(*Service)
-	// Fill root container and do changes
-	go func() {
-		fs.Refresh()
-		for {
-			Service.Changes()
-			time.Sleep(fs.Config.RefreshTime)
-		}
-	}()
-}

+ 128 - 29
internal/fs/dropboxfs/service.go

@@ -1,12 +1,17 @@
 package dropboxfs
 
 import (
+	"bytes"
 	"io"
 	"os"
 	"strings"
 	"time"
 
+	"golang.org/x/oauth2"
+
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
 	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
+	"dev.hexasoftware.com/hxs/cloudmount/internal/oauth2util"
 
 	"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
 	dbfiles "github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
@@ -14,26 +19,103 @@ import (
 
 // Service basefs Service implementation
 type Service struct {
-	dbconfig dropbox.Config
+	dbconfig    dropbox.Config
+	savedCursor string
+}
+
+func NewService(coreConfig *core.Config) *Service {
+
+	serviceConfig := Config{}
+	log.Println("Initializing dropbox service")
+	log.Println("Source config:", coreConfig.Source)
+
+	err := core.ParseConfig(coreConfig.Source, &serviceConfig)
+	if err != nil {
+		log.Fatalf("Unable to read <source>: %v", err)
+	}
+	config := &oauth2.Config{
+		ClientID:     serviceConfig.ClientSecret.ClientID,
+		ClientSecret: serviceConfig.ClientSecret.ClientSecret,
+		RedirectURL:  "",
+		Scopes:       []string{},
+		Endpoint: oauth2.Endpoint{
+			AuthURL:  "https://www.dropbox.com/1/oauth2/authorize",
+			TokenURL: "https://api.dropbox.com/1/oauth2/token",
+		},
+	}
+	if serviceConfig.Auth == nil {
+		tok := oauth2util.GetTokenFromWeb(config)
+		serviceConfig.Auth = tok
+		core.SaveConfig(coreConfig.Source, &serviceConfig)
+	}
+
+	dbconfig := dropbox.Config{Token: serviceConfig.Auth.AccessToken}
+
+	return &Service{dbconfig: dbconfig}
+
 }
 
-func (s *Service) Changes() error {
-	log.Println("Checking changes")
+// Changes dropbox longpool changes
+func (s *Service) Changes() ([]*basefs.Change, error) {
 	fileService := dbfiles.New(s.dbconfig)
 	if fileService == nil {
 		log.Println("File service is nill")
-		return nil
+		return nil, nil
 	}
 
-	res, err := fileService.ListFolderLongpoll(dbfiles.NewListFolderLongpollArg(""))
+	if s.savedCursor == "" {
+		res, err := fileService.ListFolderGetLatestCursor(&dbfiles.ListFolderArg{Path: "", Recursive: true})
+		if err != nil {
+			log.Println("Err:", err)
+			return nil, err
+		}
+		s.savedCursor = res.Cursor
+	}
+
+	res, err := fileService.ListFolderLongpoll(dbfiles.NewListFolderLongpollArg(s.savedCursor))
 	if err != nil {
 		log.Println("Err in longpoll", err)
-		return nil
+		return nil, err
 	}
-	log.Println("Has Changes:", res.Changes)
-	log.Println("Backoff:", res.Backoff)
 
-	return nil
+	if res.Changes == false {
+		return nil, nil
+	}
+
+	ret := []*basefs.Change{}
+	for {
+		res, err := fileService.ListFolderContinue(dbfiles.NewListFolderContinueArg(s.savedCursor))
+		if err != nil {
+			return nil, err
+		}
+		for _, e := range res.Entries {
+			var change *basefs.Change
+			switch t := e.(type) {
+			case *dbfiles.DeletedMetadata:
+				change = &basefs.Change{t.PathLower, File(t), true}
+			case *dbfiles.FileMetadata:
+				change = &basefs.Change{t.PathLower, File(t), false}
+			case *dbfiles.FolderMetadata:
+				change = &basefs.Change{t.PathLower, File(t), false}
+			}
+			ret = append(ret, change)
+
+		}
+		if !res.HasMore {
+			break
+		}
+	}
+	{
+		// Store new token
+		res, err := fileService.ListFolderGetLatestCursor(&dbfiles.ListFolderArg{Path: "", Recursive: true})
+		if err != nil {
+			log.Println("Err:", err)
+			return nil, err
+		}
+		s.savedCursor = res.Cursor
+	}
+
+	return ret, nil
 }
 
 // ListAll implementation
@@ -45,7 +127,7 @@ func (s *Service) ListAll() ([]*basefs.File, error) {
 	var err error
 	var res *dbfiles.ListFolderResult
 
-	res, err = fileService.ListFolder(&dbfiles.ListFolderArg{Recursive: true, Path: "/test", IncludeDeleted: false, IncludeMediaInfo: false})
+	res, err = fileService.ListFolder(&dbfiles.ListFolderArg{Recursive: true, Path: "", IncludeDeleted: false, IncludeMediaInfo: false})
 	if err != nil {
 		log.Println("Error listing:", err)
 		return nil, err
@@ -70,26 +152,35 @@ func (s *Service) ListAll() ([]*basefs.File, error) {
 func (s *Service) Create(parent *basefs.File, name string, isDir bool) (*basefs.File, error) {
 	fileService := dbfiles.New(s.dbconfig)
 
+	parentID := ""
+	if parent != nil {
+		parentID = parent.ID
+	}
 	if isDir {
 		data, err := fileService.CreateFolder(&dbfiles.CreateFolderArg{
 			Autorename: false,
-			Path:       parent.ID + "/" + name,
+			Path:       parentID + "/" + name,
 		})
 		if err != nil {
 			return nil, err
 		}
 		return File(data), nil
 	}
-	newPath := parent.ID + "/" + name
-	// For file we create Local entry but do nothing, since dropbox does not upload local empty files
-	metadata := &dbfiles.FileMetadata{}
 
-	metadata.Id = newPath
-	metadata.Name = name
-	metadata.ServerModified = time.Now().UTC()
-	metadata.PathLower = strings.ToLower(newPath)
+	newPath := parentID + "/" + name
+	reader := bytes.NewBuffer([]byte{})
+	data, err := fileService.Upload(&dbfiles.CommitInfo{
+		Path:       newPath, // ???
+		Autorename: false,
+		Mode:       &dbfiles.WriteMode{Tagged: dropbox.Tagged{Tag: dbfiles.WriteModeOverwrite}},
+	}, reader)
+	if err != nil {
+		log.Println("Upload Error:", err)
+		return nil, err
+	}
+
+	return File(data), nil
 
-	return File(metadata), nil
 }
 
 // Upload file implementation
@@ -129,10 +220,15 @@ func (s *Service) DownloadTo(w io.Writer, file *basefs.File) error {
 func (s *Service) Move(file *basefs.File, newParent *basefs.File, name string) (*basefs.File, error) {
 	fileService := dbfiles.New(s.dbconfig)
 
+	newParentID := ""
+	if newParent != nil {
+		newParentID = newParent.ID
+	}
+
 	res, err := fileService.Move(&dbfiles.RelocationArg{
 		RelocationPath: dbfiles.RelocationPath{
 			FromPath: file.ID,
-			ToPath:   newParent.ID + "/" + name,
+			ToPath:   newParentID + "/" + name,
 		},
 	})
 	if err != nil {
@@ -164,23 +260,27 @@ func File(metadata dbfiles.IsMetadata) *basefs.File {
 
 	//var parentID string
 
+	// Common data
+
+	var md dbfiles.Metadata
 	switch t := metadata.(type) {
 	case *dbfiles.FileMetadata:
-		ID = t.PathLower
-		name = t.Name
+		md = t.Metadata
 		modifiedTime = t.ServerModified
 		size = t.Size
 	case *dbfiles.FolderMetadata:
-		ID = t.PathLower
-		name = t.Name
-		mode = os.FileMode(0755) | os.ModeDir
+		md = t.Metadata
 		modifiedTime = time.Now()
-		//parentID = t.SharedFolderId
+		mode = os.FileMode(0755) | os.ModeDir
+	//parentID = t.SharedFolderId
+	case *dbfiles.DeletedMetadata:
+		md = t.Metadata
 	}
 
-	createdTime := modifiedTime
+	ID = md.PathLower
+	name = md.Name
 
-	//log.Println("ParentID:", parentID)
+	createdTime := modifiedTime // we dont have created time on dropbox?
 
 	pathParts := strings.Split(ID, "/")
 	parentID := strings.Join(pathParts[:len(pathParts)-1], "/")
@@ -199,7 +299,6 @@ func File(metadata dbfiles.IsMetadata) *basefs.File {
 		AccessedTime: modifiedTime,
 		Mode:         mode,
 	}
-	//log.Println("FileID is:", file.ID)
 
 	return file
 }

+ 0 - 91
internal/fs/gdrivefs/client.go

@@ -1,91 +0,0 @@
-// Oauth2 google api for Drive api
-
-package gdrivefs
-
-import (
-	"context"
-	"fmt"
-
-	drive "google.golang.org/api/drive/v3"
-
-	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
-
-	"golang.org/x/oauth2"
-)
-
-func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
-	authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
-
-	fmt.Printf(
-		`Go to the following link in your browser: 
-----------------------------------------------------------------------------------------------
-%v
-----------------------------------------------------------------------------------------------
-
-type the authorization code: `, authURL)
-
-	var code string
-	if _, err := fmt.Scan(&code); err != nil {
-		log.Fatalf("Unable to read authorization code %v", err)
-	}
-
-	tok, err := config.Exchange(oauth2.NoContext, code)
-	if err != nil {
-		log.Fatalf("Unable to retrieve token from web: %v", err)
-	}
-
-	return tok
-}
-
-// Init driveService
-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:", fs.Config.Source)
-
-	err := core.ParseConfig(fs.Config.Source, fs.serviceConfig)
-
-	//b, err := ioutil.ReadFile(d.config.Source)
-
-	//b, err := ioutil.ReadFile(filepath.Join(configPath, "client_secret.json"))
-	if err != nil {
-		log.Fatalf("Unable to read client secret file: %v", err)
-	}
-	config := &oauth2.Config{
-		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{
-			AuthURL:  "https://accounts.google.com/o/oauth2/auth",  //d.serviceConfig.ClientSecret.AuthURI,
-			TokenURL: "https://accounts.google.com/o/oauth2/token", //d.serviceConfig.ClientSecret.TokenURI,
-		},
-	}
-	// We can deal with oauthToken here too
-
-	if fs.serviceConfig.Auth == nil {
-		tok := getTokenFromWeb(config)
-		fs.serviceConfig.Auth = tok
-		core.SaveConfig(fs.Config.Source, fs.serviceConfig)
-	}
-
-	/*config, err := google.ConfigFromJSON(b, drive.DriveScope)
-	if err != nil {
-		log.Fatalf("Unable to parse client secret file: %v", err)
-	}*/
-
-	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
-
-}

+ 4 - 1
internal/fs/gdrivefs/config.go

@@ -8,5 +8,8 @@ type Config struct {
 		ClientSecret string `json:"client_secret" yaml:"client_secret"`
 	} `json:"client_secret" yaml:"client_secret"`
 
-	Auth *oauth2.Token `json:"auth" yaml:"auth"`
+	Auth    *oauth2.Token `json:"auth" yaml:"auth"`
+	Options struct {
+		Safemode bool
+	}
 }

+ 3 - 58
internal/fs/gdrivefs/gdrivefs.go

@@ -1,8 +1,6 @@
 package gdrivefs
 
 import (
-	"time"
-
 	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
 	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
 	"dev.hexasoftware.com/hxs/prettylog"
@@ -12,64 +10,11 @@ var (
 	log = prettylog.New("gdrivefs")
 )
 
-type GDriveFS struct {
-	*basefs.BaseFS
-	serviceConfig *Config
-	nextRefresh   time.Time
-	//client        *drive.Service
-}
-
+// New new Filesystem implementation based on gdrive Service
 func New(core *core.Core) core.DriverFS {
 
-	fs := &GDriveFS{
-		BaseFS:        basefs.New(core),
-		serviceConfig: &Config{},
-		nextRefresh:   time.Now(),
-	}
-	client := fs.initClient() // Init Oauth2 client
-
-	//fs.BaseFS.Client = client // This will be removed
-	fs.BaseFS.Service = &Service{client: client}
+	fs := basefs.New(core)
+	fs.Service = NewService(&core.Config)
 
 	return fs
 }
-
-// Start will loop to update File entries
-func (fs *GDriveFS) Start() {
-	go func() {
-		fs.Refresh() // First load
-
-		for {
-			fs.CheckForChanges() // Loop
-			time.Sleep(fs.Config.RefreshTime)
-		}
-		// Change reader loop
-	}()
-}
-
-func (fs *GDriveFS) CheckForChanges() {
-
-	Service := fs.Service.(*Service) // Our Service
-
-	changes, err := Service.Changes()
-	if err != nil {
-		return
-	}
-	for _, c := range changes {
-		entry := fs.Root.FindByID(c.FileId)
-		if c.Removed {
-			if entry == nil {
-				continue
-			} else {
-				fs.Root.RemoveEntry(entry)
-			}
-			continue
-		}
-		if entry != nil {
-			entry.SetFile(File(c.File), fs.Config.UID, fs.Config.GID)
-		} else {
-			//Create new one
-			fs.Root.FileEntry(File(c.File)) // Creating new one
-		}
-	}
-}

+ 58 - 13
internal/fs/gdrivefs/service.go

@@ -6,9 +6,11 @@ import (
 	"os"
 	"time"
 
-	"github.com/jacobsa/fuse"
+	"golang.org/x/oauth2"
 
+	"dev.hexasoftware.com/hxs/cloudmount/internal/core"
 	"dev.hexasoftware.com/hxs/cloudmount/internal/fs/basefs"
+	"dev.hexasoftware.com/hxs/cloudmount/internal/oauth2util"
 
 	drive "google.golang.org/api/drive/v3"
 	"google.golang.org/api/googleapi"
@@ -24,7 +26,44 @@ type Service struct {
 	savedStartPageToken string
 }
 
-func (s *Service) Changes() ([]*drive.Change, error) { // Return a list of New file entries
+func NewService(coreConfig *core.Config) *Service {
+
+	serviceConfig := Config{}
+	log.Println("Initializing gdrive service")
+	log.Println("Source config:", coreConfig.Source)
+
+	err := core.ParseConfig(coreConfig.Source, &serviceConfig)
+	if err != nil {
+		log.Fatalf("Unable to read <source>: %v", err)
+	}
+	config := &oauth2.Config{
+		ClientID:     serviceConfig.ClientSecret.ClientID,
+		ClientSecret: serviceConfig.ClientSecret.ClientSecret,
+		RedirectURL:  "urn:ietf:wg:oauth:2.0:oob", //d.serviceConfig.ClientSecret.RedirectURIs[0],
+		Scopes:       []string{drive.DriveScope},
+		Endpoint: oauth2.Endpoint{
+			AuthURL:  "https://accounts.google.com/o/oauth2/auth",  //d.serviceConfig.ClientSecret.AuthURI,
+			TokenURL: "https://accounts.google.com/o/oauth2/token", //d.serviceConfig.ClientSecret.TokenURI,
+		},
+	}
+	if serviceConfig.Auth == nil {
+		tok := oauth2util.GetTokenFromWeb(config)
+		serviceConfig.Auth = tok
+		core.SaveConfig(coreConfig.Source, &serviceConfig)
+	}
+
+	client := config.Client(oauth2.NoContext, serviceConfig.Auth)
+	driveCli, err := drive.New(client)
+	if err != nil {
+		log.Fatalf("Unable to retrieve drive Client: %v", err)
+	}
+
+	return &Service{client: driveCli}
+
+}
+
+//func (s *Service) Changes() ([]*drive.Change, error) { // Return a list of New file entries
+func (s *Service) Changes() ([]*basefs.Change, error) { // Return a list of New file entries
 	if s.savedStartPageToken == "" {
 		startPageTokenRes, err := s.client.Changes.GetStartPageToken().Do()
 		if err != nil {
@@ -33,7 +72,7 @@ func (s *Service) Changes() ([]*drive.Change, error) { // Return a list of New f
 		s.savedStartPageToken = startPageTokenRes.StartPageToken
 	}
 
-	ret := []*drive.Change{}
+	ret := []*basefs.Change{}
 	pageToken := s.savedStartPageToken
 	for pageToken != "" {
 		changesRes, err := s.client.Changes.List(pageToken).Fields(googleapi.Field("newStartPageToken,nextPageToken,changes(removed,fileId,file(" + fileFields + "))")).Do()
@@ -43,7 +82,8 @@ func (s *Service) Changes() ([]*drive.Change, error) { // Return a list of New f
 		}
 		//log.Println("Changes:", len(changesRes.Changes))
 		for _, c := range changesRes.Changes {
-			ret = append(ret, c) // Convert to our changes
+			change := &basefs.Change{ID: c.FileId, File: File(c.File), Remove: c.Removed}
+			ret = append(ret, change) // Convert to our changes
 		}
 		if changesRes.NewStartPageToken != "" {
 			s.savedStartPageToken = changesRes.NewStartPageToken
@@ -127,6 +167,10 @@ func (s *Service) ListAll() ([]*basefs.File, error) {
 }
 
 func (s *Service) Create(parent *basefs.File, name string, isDir bool) (*basefs.File, error) {
+	if parent == nil {
+		return nil, basefs.ErrPermission
+	}
+
 	newGFile := &drive.File{
 		Parents: []string{parent.ID},
 		Name:    name,
@@ -137,7 +181,8 @@ func (s *Service) Create(parent *basefs.File, name string, isDir bool) (*basefs.
 	// Could be transformed to CreateFile in continer
 	createdGFile, err := s.client.Files.Create(newGFile).Fields(fileFields).Do()
 	if err != nil {
-		return nil, fuse.EINVAL
+		log.Println("err", err)
+		return nil, err
 	}
 
 	return File(createdGFile), nil
@@ -172,7 +217,7 @@ func (s *Service) DownloadTo(w io.Writer, file *basefs.File) error {
 	}
 
 	if err != nil {
-		log.Println("Error from GDrive API", err)
+		log.Println("Error from GDrive API", err, "Mimetype:", gfile.MimeType)
 		return err
 	}
 	defer res.Body.Close()
@@ -182,7 +227,9 @@ func (s *Service) DownloadTo(w io.Writer, file *basefs.File) error {
 }
 
 func (s *Service) Move(file *basefs.File, newParent *basefs.File, name string) (*basefs.File, error) {
-
+	/*if newParent == nil {
+		return nil, basefs.ErrPermission
+	}*/
 	ngFile := &drive.File{
 		Name: name,
 	}
@@ -193,19 +240,17 @@ func (s *Service) Move(file *basefs.File, newParent *basefs.File, name string) (
 		for _, pgid := range file.Parents {
 			updateCall.RemoveParents(pgid) // Remove all parents??
 		}
-		updateCall.AddParents(newParent.ID)
+		if newParent != nil {
+			updateCall.AddParents(newParent.ID)
+		}
 	}
-	//	}
-	/*if oldParentFile != newParentFile {
-		updateCall.RemoveParents(oldParentFile.GID)
-		updateCall.AddParents(newParentFile.GID)
-	}*/
 	updatedFile, err := updateCall.Do()
 
 	return File(updatedFile), err
 }
 
 func (s *Service) Delete(file *basefs.File) error {
+	// PRevent removing from root?
 	err := s.client.Files.Delete(file.ID).Do()
 	if err != nil {
 		return err

+ 34 - 0
internal/oauth2util/oauth2util.go

@@ -0,0 +1,34 @@
+package oauth2util
+
+import (
+	"fmt"
+	"log"
+
+	"golang.org/x/oauth2"
+)
+
+//GetTokenFromWeb shows link to user, and requests the informed token
+func GetTokenFromWeb(config *oauth2.Config) *oauth2.Token {
+	//authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
+	authURL := config.AuthCodeURL("state-token")
+
+	fmt.Printf(
+		`Go to the following link in your browser: 
+----------------------------------------------------------------------------------------------
+%v
+----------------------------------------------------------------------------------------------
+
+type the authorization code: `, authURL)
+
+	var code string
+	if _, err := fmt.Scan(&code); err != nil {
+		log.Fatalf("Unable to read authorization code %v", err)
+	}
+
+	tok, err := config.Exchange(oauth2.NoContext, code)
+	if err != nil {
+		log.Fatalf("Unable to retrieve token from web: %v", err)
+	}
+
+	return tok
+}

+ 10 - 0
main.go

@@ -7,6 +7,7 @@ package main
 
 import (
 	"fmt"
+	"net/http"
 	"os"
 
 	"os/exec"
@@ -23,6 +24,15 @@ var (
 )
 
 func main() {
+	// TODO: TEMP
+	{
+		// Globally insecure SSL for debugging
+		r, _ := http.NewRequest("GET", "http://localhost", nil)
+		cli := &http.Client{}
+		cli.Do(r)
+		tr := http.DefaultTransport.(*http.Transport)
+		tr.TLSClientConfig.InsecureSkipVerify = true
+	}
 
 	prettylog.Global()
 

+ 2 - 1
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files/client.go

@@ -1803,7 +1803,8 @@ type ListFolderLongpollAPIError struct {
 }
 
 func (dbx *apiImpl) ListFolderLongpoll(arg *ListFolderLongpollArg) (res *ListFolderLongpollResult, err error) {
-	cli := dbx.Client
+	//cli := dbx.Client
+	cli := http.Client{}
 
 	if dbx.Config.Verbose {
 		log.Printf("arg: %v", arg)

+ 1 - 0
vendor/github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/sdk.go

@@ -95,6 +95,7 @@ func (c *Context) NewRequest(
 	}
 	if !authed {
 		req.Header.Del("Authorization")
+
 	}
 	return req, nil
 }

+ 1 - 1
version.go

@@ -2,5 +2,5 @@ package main
 
 const (
   //Version contains version of the package
-  Version = "0.4-1-g48bf168 - built: 2017-07-11 17:00:31 UTC"
+  Version = "0.4-4-g806e41c - built: 2017-07-15 21:01:58 UTC"
 )