forked from TrueCloudLab/rclone
dropbox: Use /delta to list objects - much quicker
Also fix major performance problem - re-reading entry each time!
This commit is contained in:
parent
c9aca33030
commit
d2f187e1a1
1 changed files with 52 additions and 37 deletions
|
@ -4,15 +4,7 @@ package dropbox
|
||||||
/*
|
/*
|
||||||
Limitations of dropbox
|
Limitations of dropbox
|
||||||
|
|
||||||
File system is case insensitive!
|
File system is case insensitive
|
||||||
|
|
||||||
Can only have 25,000 objects in a directory
|
|
||||||
|
|
||||||
FIXME /delta might be more efficient than recursing with Metadata
|
|
||||||
|
|
||||||
FIXME do we need synchronisation for any of the dropbox calls?
|
|
||||||
- added locking for datastore
|
|
||||||
- fixes concurrency there
|
|
||||||
|
|
||||||
FIXME need to delete metadata when we delete files!
|
FIXME need to delete metadata when we delete files!
|
||||||
|
|
||||||
|
@ -21,6 +13,7 @@ Failed to copy: Upload failed: invalid character '<' looking for beginning of va
|
||||||
This is a JSON decode error - from Update / UploadByChunk
|
This is a JSON decode error - from Update / UploadByChunk
|
||||||
- Caused by 500 error from dropbox
|
- Caused by 500 error from dropbox
|
||||||
- See https://github.com/stacktic/dropbox/issues/1
|
- See https://github.com/stacktic/dropbox/issues/1
|
||||||
|
- Possibly confusing dropbox with excess concurrency?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -101,7 +94,8 @@ func Config(name string) {
|
||||||
type FsDropbox struct {
|
type FsDropbox struct {
|
||||||
db *dropbox.Dropbox // the connection to the dropbox server
|
db *dropbox.Dropbox // the connection to the dropbox server
|
||||||
root string // the path we are working on
|
root string // the path we are working on
|
||||||
slashRoot string // root with "/" prefix and postix
|
slashRoot string // root with "/" prefix
|
||||||
|
slashRootSlash string // root with "/" prefix and postix
|
||||||
datastoreManager *dropbox.DatastoreManager
|
datastoreManager *dropbox.DatastoreManager
|
||||||
datastore *dropbox.Datastore
|
datastore *dropbox.Datastore
|
||||||
table *dropbox.Table
|
table *dropbox.Table
|
||||||
|
@ -157,12 +151,14 @@ func NewFs(name, path string) (fs.Fs, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
slashRoot := "/" + root
|
slashRoot := "/" + root
|
||||||
|
slashRootSlash := slashRoot
|
||||||
if root != "" {
|
if root != "" {
|
||||||
slashRoot += "/"
|
slashRootSlash += "/"
|
||||||
}
|
}
|
||||||
f := &FsDropbox{
|
f := &FsDropbox{
|
||||||
root: root,
|
root: root,
|
||||||
slashRoot: slashRoot,
|
slashRoot: slashRoot,
|
||||||
|
slashRootSlash: slashRootSlash,
|
||||||
db: db,
|
db: db,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +192,7 @@ func (f *FsDropbox) newFsObjectWithInfo(remote string, info *dropbox.Entry) (fs.
|
||||||
dropbox: f,
|
dropbox: f,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
}
|
}
|
||||||
if info == nil {
|
if info != nil {
|
||||||
o.setMetadataFromEntry(info)
|
o.setMetadataFromEntry(info)
|
||||||
} else {
|
} else {
|
||||||
err := o.readEntryAndSetMetadata()
|
err := o.readEntryAndSetMetadata()
|
||||||
|
@ -227,30 +223,47 @@ func (f *FsDropbox) NewFsObject(remote string) fs.Object {
|
||||||
// Strips the root off entry and returns it
|
// Strips the root off entry and returns it
|
||||||
func (f *FsDropbox) stripRoot(entry *dropbox.Entry) string {
|
func (f *FsDropbox) stripRoot(entry *dropbox.Entry) string {
|
||||||
path := entry.Path
|
path := entry.Path
|
||||||
if strings.HasPrefix(path, f.slashRoot) {
|
if strings.HasPrefix(path, f.slashRootSlash) {
|
||||||
path = path[len(f.slashRoot):]
|
path = path[len(f.slashRootSlash):]
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the path returning a channel of FsObjects
|
// Walk the root returning a channel of FsObjects
|
||||||
//
|
func (f *FsDropbox) list(out fs.ObjectsChan) {
|
||||||
// FIXME could do this in parallel but needs to be limited to Checkers
|
cursor := ""
|
||||||
func (f *FsDropbox) list(path string, out fs.ObjectsChan) {
|
for {
|
||||||
entry, err := f.db.Metadata(f.slashRoot+path, true, false, "", "", metadataLimit)
|
deltaPage, err := f.db.Delta(cursor, f.slashRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Stats.Error()
|
fs.Stats.Error()
|
||||||
fs.Log(f, "Couldn't list %q: %s", path, err)
|
fs.Log(f, "Couldn't list: %s", err)
|
||||||
|
break
|
||||||
} else {
|
} else {
|
||||||
for i := range entry.Contents {
|
if deltaPage.Reset && cursor != "" {
|
||||||
entry := &entry.Contents[i]
|
fs.Log(f, "Unexpected reset during listing - try again")
|
||||||
path = f.stripRoot(entry)
|
fs.Stats.Error()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fs.Debug(f, "%d delta entries received", len(deltaPage.Entries))
|
||||||
|
for i := range deltaPage.Entries {
|
||||||
|
deltaEntry := &deltaPage.Entries[i]
|
||||||
|
entry := deltaEntry.Entry
|
||||||
|
if entry == nil {
|
||||||
|
// This notifies of a deleted object which we ignore
|
||||||
|
continue
|
||||||
|
}
|
||||||
if entry.IsDir {
|
if entry.IsDir {
|
||||||
f.list(path, out)
|
// ignore directories
|
||||||
} else {
|
} else {
|
||||||
|
path := f.stripRoot(entry)
|
||||||
out <- f.NewFsObjectWithInfo(path, entry)
|
out <- f.NewFsObjectWithInfo(path, entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !deltaPage.HasMore {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cursor = deltaPage.Cursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +272,7 @@ func (f *FsDropbox) List() fs.ObjectsChan {
|
||||||
out := make(fs.ObjectsChan, fs.Config.Checkers)
|
out := make(fs.ObjectsChan, fs.Config.Checkers)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(out)
|
defer close(out)
|
||||||
f.list("", out)
|
f.list(out)
|
||||||
}()
|
}()
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
@ -326,7 +339,7 @@ func (f *FsDropbox) Mkdir() error {
|
||||||
//
|
//
|
||||||
// Returns an error if it isn't empty
|
// Returns an error if it isn't empty
|
||||||
func (f *FsDropbox) Rmdir() error {
|
func (f *FsDropbox) Rmdir() error {
|
||||||
entry, err := f.db.Metadata(f.slashRoot, true, false, "", "", metadataLimit)
|
entry, err := f.db.Metadata(f.slashRoot, true, false, "", "", 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -463,13 +476,13 @@ func (o *FsObjectDropbox) readEntryAndSetMetadata() error {
|
||||||
|
|
||||||
// Returns the remote path for the object
|
// Returns the remote path for the object
|
||||||
func (o *FsObjectDropbox) remotePath() string {
|
func (o *FsObjectDropbox) remotePath() string {
|
||||||
return o.dropbox.slashRoot + o.remote
|
return o.dropbox.slashRootSlash + o.remote
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the key for the metadata database
|
// Returns the key for the metadata database
|
||||||
func (o *FsObjectDropbox) metadataKey() string {
|
func (o *FsObjectDropbox) metadataKey() string {
|
||||||
// NB File system is case insensitive
|
// NB File system is case insensitive
|
||||||
key := strings.ToLower(o.dropbox.slashRoot + o.remote)
|
key := strings.ToLower(o.remotePath())
|
||||||
return fmt.Sprintf("%x", md5.Sum([]byte(key)))
|
return fmt.Sprintf("%x", md5.Sum([]byte(key)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,6 +492,7 @@ func (o *FsObjectDropbox) readMetaData() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fs.Debug(o, "Reading metadata from datastore")
|
||||||
o.dropbox.datastoreMutex.Lock()
|
o.dropbox.datastoreMutex.Lock()
|
||||||
record, err := o.dropbox.table.Get(o.metadataKey())
|
record, err := o.dropbox.table.Get(o.metadataKey())
|
||||||
o.dropbox.datastoreMutex.Unlock()
|
o.dropbox.datastoreMutex.Unlock()
|
||||||
|
@ -547,6 +561,7 @@ func (o *FsObjectDropbox) ModTime() time.Time {
|
||||||
// FIXME if we don't set md5sum what will that do?
|
// FIXME if we don't set md5sum what will that do?
|
||||||
func (o *FsObjectDropbox) setModTimeAndMd5sum(modTime time.Time, md5sum string) error {
|
func (o *FsObjectDropbox) setModTimeAndMd5sum(modTime time.Time, md5sum string) error {
|
||||||
key := o.metadataKey()
|
key := o.metadataKey()
|
||||||
|
// fs.Debug(o, "Writing metadata to datastore")
|
||||||
return o.dropbox.transaction(func() error {
|
return o.dropbox.transaction(func() error {
|
||||||
record, err := o.dropbox.table.GetOrInsert(key)
|
record, err := o.dropbox.table.GetOrInsert(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue