2014-07-08 22:59:30 +02:00
// Dropbox interface
package dropbox
/ *
Limitations of dropbox
2014-07-12 12:46:45 +02:00
File system is case insensitive
2014-07-10 01:17:40 +02:00
2014-07-13 11:53:53 +02:00
The datastore is limited to 100 , 000 records which therefore is the
limit of the number of files that rclone can use on dropbox .
FIXME only open datastore if we need it ?
2014-07-11 18:21:23 +02:00
FIXME Getting this sometimes
Failed to copy : Upload failed : invalid character '<' looking for beginning of value
This is a JSON decode error - from Update / UploadByChunk
- Caused by 500 error from dropbox
- See https : //github.com/stacktic/dropbox/issues/1
2014-07-12 12:46:45 +02:00
- Possibly confusing dropbox with excess concurrency ?
2015-05-10 12:25:54 +02:00
FIXME implement timeouts - need to get "github.com/stacktic/dropbox"
and hence "golang.org/x/oauth2" which uses DefaultTransport unless it
is set in the context passed into . Client ( )
func ( db * Dropbox ) client ( ) * http . Client {
return db . config . Client ( oauth2 . NoContext , db . token )
}
// HTTPClient is the context key to use with golang.org/x/net/context's
// WithValue function to associate an *http.Client value with a context.
var HTTPClient ContextKey
So pass in a context with HTTPClient set ...
2014-07-08 22:59:30 +02:00
* /
import (
"crypto/md5"
"errors"
"fmt"
"io"
"log"
2014-07-14 12:24:04 +02:00
"path"
2014-07-08 22:59:30 +02:00
"strings"
2014-07-11 18:21:23 +02:00
"sync"
2014-07-08 22:59:30 +02:00
"time"
"github.com/ncw/rclone/fs"
"github.com/stacktic/dropbox"
)
// Constants
const (
2014-07-11 18:21:23 +02:00
rcloneAppKey = "5jcck7diasz0rqy"
rcloneAppSecret = "1n9m04y2zx7bf26"
uploadChunkSize = 64 * 1024 // chunk size for upload
metadataLimit = dropbox . MetadataLimitDefault // max items to fetch at once
datastoreName = "rclone"
tableName = "metadata"
md5sumField = "md5sum"
mtimeField = "mtime"
maxCommitRetries = 5
2014-07-29 18:50:07 +02:00
timeFormatIn = time . RFC3339
timeFormatOut = "2006-01-02T15:04:05.000000000Z07:00"
2014-07-08 22:59:30 +02:00
)
// Register with Fs
func init ( ) {
fs . Register ( & fs . FsInfo {
Name : "dropbox" ,
NewFs : NewFs ,
2014-07-29 18:50:07 +02:00
Config : configHelper ,
2014-07-08 22:59:30 +02:00
Options : [ ] fs . Option { {
Name : "app_key" ,
Help : "Dropbox App Key - leave blank to use rclone's." ,
} , {
Name : "app_secret" ,
Help : "Dropbox App Secret - leave blank to use rclone's." ,
} } ,
} )
}
// Configuration helper - called after the user has put in the defaults
2014-07-29 18:50:07 +02:00
func configHelper ( name string ) {
2014-07-08 22:59:30 +02:00
// See if already have a token
token := fs . ConfigFile . MustValue ( name , "token" )
if token != "" {
fmt . Printf ( "Already have a dropbox token - refresh?\n" )
if ! fs . Confirm ( ) {
return
}
}
// Get a dropbox
db := newDropbox ( name )
// This method will ask the user to visit an URL and paste the generated code.
if err := db . Auth ( ) ; err != nil {
log . Fatalf ( "Failed to authorize: %v" , err )
}
// Get the token
token = db . AccessToken ( )
// Stuff it in the config file if it has changed
old := fs . ConfigFile . MustValue ( name , "token" )
if token != old {
fs . ConfigFile . SetValue ( name , "token" , token )
fs . SaveConfig ( )
}
}
// FsDropbox represents a remote dropbox server
type FsDropbox struct {
2014-07-10 01:17:40 +02:00
db * dropbox . Dropbox // the connection to the dropbox server
root string // the path we are working on
2015-05-23 20:56:48 +02:00
slashRoot string // root with "/" prefix, lowercase
slashRootSlash string // root with "/" prefix and postfix, lowercase
2014-07-10 01:17:40 +02:00
datastoreManager * dropbox . DatastoreManager
datastore * dropbox . Datastore
table * dropbox . Table
2014-07-11 18:21:23 +02:00
datastoreMutex sync . Mutex // lock this when using the datastore
2014-07-12 13:38:30 +02:00
datastoreErr error // pending errors on the datastore
2014-07-08 22:59:30 +02:00
}
// FsObjectDropbox describes a dropbox object
type FsObjectDropbox struct {
dropbox * FsDropbox // what this object is part of
remote string // The remote path
md5sum string // md5sum of the object
bytes int64 // size of the object
modTime time . Time // time it was last modified
}
// ------------------------------------------------------------
// String converts this FsDropbox to a string
func ( f * FsDropbox ) String ( ) string {
return fmt . Sprintf ( "Dropbox root '%s'" , f . root )
}
// Makes a new dropbox from the config
func newDropbox ( name string ) * dropbox . Dropbox {
db := dropbox . NewDropbox ( )
appKey := fs . ConfigFile . MustValue ( name , "app_key" )
if appKey == "" {
appKey = rcloneAppKey
}
appSecret := fs . ConfigFile . MustValue ( name , "app_secret" )
if appSecret == "" {
appSecret = rcloneAppSecret
}
db . SetAppInfo ( appKey , appSecret )
return db
}
// NewFs contstructs an FsDropbox from the path, container:path
2014-07-14 12:24:04 +02:00
func NewFs ( name , root string ) ( fs . Fs , error ) {
2014-07-08 22:59:30 +02:00
db := newDropbox ( name )
f := & FsDropbox {
2014-07-14 12:24:04 +02:00
db : db ,
2014-07-08 22:59:30 +02:00
}
2014-07-14 12:24:04 +02:00
f . setRoot ( root )
2014-07-08 22:59:30 +02:00
// Read the token from the config file
token := fs . ConfigFile . MustValue ( name , "token" )
// Authorize the client
db . SetAccessToken ( token )
2014-07-10 01:17:40 +02:00
// Make a db to store rclone metadata in
f . datastoreManager = db . NewDatastoreManager ( )
2014-07-12 13:38:30 +02:00
// Open the datastore in the background
2014-07-14 12:24:04 +02:00
go f . openDataStore ( )
// See if the root is actually an object
entry , err := f . db . Metadata ( f . slashRoot , false , false , "" , "" , metadataLimit )
if err == nil && ! entry . IsDir {
remote := path . Base ( f . root )
newRoot := path . Dir ( f . root )
if newRoot == "." {
newRoot = ""
2014-07-12 13:38:30 +02:00
}
2014-07-14 12:24:04 +02:00
f . setRoot ( newRoot )
obj := f . NewFsObject ( remote )
// return a Fs Limited to this object
return fs . NewLimited ( f , obj ) , nil
}
2014-07-10 01:17:40 +02:00
2014-07-08 22:59:30 +02:00
return f , nil
}
2014-07-14 12:24:04 +02:00
// Sets root in f
func ( f * FsDropbox ) setRoot ( root string ) {
f . root = strings . Trim ( root , "/" )
2015-05-23 20:56:48 +02:00
lowerCaseRoot := strings . ToLower ( f . root )
f . slashRoot = "/" + lowerCaseRoot
2014-07-14 12:24:04 +02:00
f . slashRootSlash = f . slashRoot
2015-05-23 20:56:48 +02:00
if lowerCaseRoot != "" {
2014-07-14 12:24:04 +02:00
f . slashRootSlash += "/"
}
}
// Opens the datastore in f
func ( f * FsDropbox ) openDataStore ( ) {
f . datastoreMutex . Lock ( )
defer f . datastoreMutex . Unlock ( )
fs . Debug ( f , "Open rclone datastore" )
// Open the rclone datastore
var err error
f . datastore , err = f . datastoreManager . OpenDatastore ( datastoreName )
if err != nil {
fs . Log ( f , "Failed to open datastore: %v" , err )
f . datastoreErr = err
return
}
// Get the table we are using
f . table , err = f . datastore . GetTable ( tableName )
if err != nil {
fs . Log ( f , "Failed to open datastore table: %v" , err )
f . datastoreErr = err
return
}
fs . Debug ( f , "Open rclone datastore finished" )
}
2014-07-08 22:59:30 +02:00
// Return an FsObject from a path
2014-07-29 18:50:07 +02:00
//
// May return nil if an error occurred
func ( f * FsDropbox ) newFsObjectWithInfo ( remote string , info * dropbox . Entry ) fs . Object {
2014-07-10 01:17:40 +02:00
o := & FsObjectDropbox {
2014-07-08 22:59:30 +02:00
dropbox : f ,
remote : remote ,
}
2014-07-12 12:46:45 +02:00
if info != nil {
2014-07-10 01:17:40 +02:00
o . setMetadataFromEntry ( info )
2014-07-08 22:59:30 +02:00
} else {
2014-07-10 01:17:40 +02:00
err := o . readEntryAndSetMetadata ( )
2014-07-08 22:59:30 +02:00
if err != nil {
// logged already fs.Debug("Failed to read info: %s", err)
2014-07-29 18:50:07 +02:00
return nil
2014-07-08 22:59:30 +02:00
}
}
2014-07-29 18:50:07 +02:00
return o
2014-07-08 22:59:30 +02:00
}
// Return an FsObject from a path
//
// May return nil if an error occurred
func ( f * FsDropbox ) NewFsObject ( remote string ) fs . Object {
2014-07-29 18:50:07 +02:00
return f . newFsObjectWithInfo ( remote , nil )
2014-07-08 22:59:30 +02:00
}
2015-05-23 20:56:48 +02:00
// Strips the root off path and returns it
func ( f * FsDropbox ) stripRoot ( path string ) * string {
lowercase := strings . ToLower ( path )
if ! strings . HasPrefix ( lowercase , f . slashRootSlash ) {
fs . Stats . Error ( )
2015-08-08 21:10:31 +02:00
fs . ErrorLog ( f , "Path '%s' is not under root '%s'" , path , f . slashRootSlash )
2015-05-23 20:56:48 +02:00
return nil
2014-07-08 22:59:30 +02:00
}
2015-05-23 20:56:48 +02:00
stripped := path [ len ( f . slashRootSlash ) : ]
return & stripped
2014-07-08 22:59:30 +02:00
}
2014-07-12 12:46:45 +02:00
// Walk the root returning a channel of FsObjects
func ( f * FsDropbox ) list ( out fs . ObjectsChan ) {
2015-05-23 20:56:48 +02:00
// Track path component case, it could be different for entries coming from DropBox API
// See https://www.dropboxforum.com/hc/communities/public/questions/201665409-Wrong-character-case-of-folder-name-when-calling-listFolder-using-Sync-API?locale=en-us
// and https://github.com/ncw/rclone/issues/53
nameTree := NewNameTree ( )
2014-07-12 12:46:45 +02:00
cursor := ""
for {
deltaPage , err := f . db . Delta ( cursor , f . slashRoot )
if err != nil {
fs . Stats . Error ( )
2015-08-08 21:10:31 +02:00
fs . ErrorLog ( f , "Couldn't list: %s" , err )
2014-07-12 12:46:45 +02:00
break
} else {
if deltaPage . Reset && cursor != "" {
2015-08-08 21:10:31 +02:00
fs . ErrorLog ( f , "Unexpected reset during listing - try again" )
2014-07-12 12:46:45 +02:00
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 {
2014-07-13 11:53:53 +02:00
// This notifies of a deleted object
fs . Debug ( f , "Deleting metadata for %q" , deltaEntry . Path )
key := metadataKey ( deltaEntry . Path ) // Path is lowercased
err := f . deleteMetadata ( key )
if err != nil {
fs . Debug ( f , "Failed to delete metadata for %q" , deltaEntry . Path )
// Don't accumulate Error here
}
2014-07-12 12:46:45 +02:00
} else {
2015-05-23 20:56:48 +02:00
if len ( entry . Path ) <= 1 || entry . Path [ 0 ] != '/' {
fs . Stats . Error ( )
2015-08-08 21:10:31 +02:00
fs . ErrorLog ( f , "dropbox API inconsistency: a path should always start with a slash and be at least 2 characters: %s" , entry . Path )
2015-05-23 20:56:48 +02:00
continue
}
lastSlashIndex := strings . LastIndex ( entry . Path , "/" )
var parentPath string
if lastSlashIndex == 0 {
parentPath = ""
} else {
parentPath = entry . Path [ 1 : lastSlashIndex ]
}
lastComponent := entry . Path [ lastSlashIndex + 1 : ]
2014-07-13 11:53:53 +02:00
if entry . IsDir {
2015-05-23 20:56:48 +02:00
nameTree . PutCaseCorrectDirectoryName ( parentPath , lastComponent )
2014-07-13 11:53:53 +02:00
} else {
2015-05-23 20:56:48 +02:00
parentPathCorrectCase := nameTree . GetPathWithCorrectCase ( parentPath )
if parentPathCorrectCase != nil {
path := f . stripRoot ( * parentPathCorrectCase + "/" + lastComponent )
if path == nil {
// an error occurred and logged by stripRoot
continue
}
out <- f . newFsObjectWithInfo ( * path , entry )
} else {
nameTree . PutFile ( parentPath , lastComponent , entry )
}
2014-07-13 11:53:53 +02:00
}
2014-07-12 12:46:45 +02:00
}
}
if ! deltaPage . HasMore {
break
2014-07-08 22:59:30 +02:00
}
2014-12-23 13:40:53 +01:00
cursor = deltaPage . Cursor . Cursor
2014-07-08 22:59:30 +02:00
}
}
2015-05-23 20:56:48 +02:00
walkFunc := func ( caseCorrectFilePath string , entry * dropbox . Entry ) {
path := f . stripRoot ( "/" + caseCorrectFilePath )
if path == nil {
// an error occurred and logged by stripRoot
return
}
out <- f . newFsObjectWithInfo ( * path , entry )
}
nameTree . WalkFiles ( f . root , walkFunc )
2014-07-08 22:59:30 +02:00
}
// Walk the path returning a channel of FsObjects
func ( f * FsDropbox ) List ( ) fs . ObjectsChan {
out := make ( fs . ObjectsChan , fs . Config . Checkers )
go func ( ) {
defer close ( out )
2014-07-12 12:46:45 +02:00
f . list ( out )
2014-07-08 22:59:30 +02:00
} ( )
return out
}
// Walk the path returning a channel of FsObjects
func ( f * FsDropbox ) ListDir ( ) fs . DirChan {
out := make ( fs . DirChan , fs . Config . Checkers )
go func ( ) {
defer close ( out )
entry , err := f . db . Metadata ( f . root , true , false , "" , "" , metadataLimit )
if err != nil {
fs . Stats . Error ( )
2015-08-08 21:10:31 +02:00
fs . ErrorLog ( f , "Couldn't list directories in root: %s" , err )
2014-07-08 22:59:30 +02:00
} else {
for i := range entry . Contents {
entry := & entry . Contents [ i ]
if entry . IsDir {
2015-05-23 20:56:48 +02:00
name := f . stripRoot ( entry . Path )
if name == nil {
// an error occurred and logged by stripRoot
continue
}
2014-07-08 22:59:30 +02:00
out <- & fs . Dir {
2015-05-23 20:56:48 +02:00
Name : * name ,
2014-07-08 22:59:30 +02:00
When : time . Time ( entry . ClientMtime ) ,
2015-08-03 22:18:34 +02:00
Bytes : entry . Bytes ,
2014-07-08 22:59:30 +02:00
Count : - 1 ,
}
}
}
}
} ( )
return out
}
// A read closer which doesn't close the input
type readCloser struct {
in io . Reader
}
// Read bytes from the object - see io.Reader
func ( rc * readCloser ) Read ( p [ ] byte ) ( n int , err error ) {
return rc . in . Read ( p )
}
// Dummy close function
func ( rc * readCloser ) Close ( ) error {
return nil
}
// Put the object
//
// Copy the reader in to the new object which is returned
//
// The new object may have been created if an error is returned
func ( f * FsDropbox ) Put ( in io . Reader , remote string , modTime time . Time , size int64 ) ( fs . Object , error ) {
// Temporary FsObject under construction
o := & FsObjectDropbox { dropbox : f , remote : remote }
return o , o . Update ( in , modTime , size )
}
// Mkdir creates the container if it doesn't exist
func ( f * FsDropbox ) Mkdir ( ) error {
2014-07-13 11:51:47 +02:00
entry , err := f . db . Metadata ( f . slashRoot , false , false , "" , "" , metadataLimit )
if err == nil {
if entry . IsDir {
return nil
}
return fmt . Errorf ( "%q already exists as file" , f . root )
}
_ , err = f . db . CreateFolder ( f . slashRoot )
2014-07-08 22:59:30 +02:00
return err
}
// Rmdir deletes the container
//
// Returns an error if it isn't empty
func ( f * FsDropbox ) Rmdir ( ) error {
2014-07-12 12:46:45 +02:00
entry , err := f . db . Metadata ( f . slashRoot , true , false , "" , "" , 16 )
2014-07-08 22:59:30 +02:00
if err != nil {
return err
}
if len ( entry . Contents ) != 0 {
return errors . New ( "Directory not empty" )
}
return f . Purge ( )
}
// Return the precision
func ( fs * FsDropbox ) Precision ( ) time . Duration {
2014-07-10 01:17:40 +02:00
return time . Nanosecond
2014-07-08 22:59:30 +02:00
}
// Purge deletes all the files and the container
//
2014-07-13 11:53:53 +02:00
// Optional interface: Only implement this if you have a way of
// deleting all the files quicker than just running Remove() on the
// result of List()
2014-07-08 22:59:30 +02:00
func ( f * FsDropbox ) Purge ( ) error {
2014-07-13 11:53:53 +02:00
// Delete metadata first
var wg sync . WaitGroup
to_be_deleted := f . List ( )
wg . Add ( fs . Config . Transfers )
for i := 0 ; i < fs . Config . Transfers ; i ++ {
go func ( ) {
defer wg . Done ( )
for dst := range to_be_deleted {
o := dst . ( * FsObjectDropbox )
o . deleteMetadata ( )
}
} ( )
}
wg . Wait ( )
// Let dropbox delete the filesystem tree
2014-07-08 22:59:30 +02:00
_ , err := f . db . Delete ( f . slashRoot )
return err
}
2014-07-11 18:21:23 +02:00
// Tries the transaction in fn then calls commit, repeating until retry limit
//
// Holds datastore mutex while in progress
func ( f * FsDropbox ) transaction ( fn func ( ) error ) error {
f . datastoreMutex . Lock ( )
defer f . datastoreMutex . Unlock ( )
2014-07-12 13:38:30 +02:00
if f . datastoreErr != nil {
return f . datastoreErr
}
2014-07-11 18:21:23 +02:00
var err error
for i := 1 ; i <= maxCommitRetries ; i ++ {
err = fn ( )
if err != nil {
return err
}
err = f . datastore . Commit ( )
if err == nil {
break
}
fs . Debug ( f , "Retrying transaction %d/%d" , i , maxCommitRetries )
}
if err != nil {
return fmt . Errorf ( "Failed to commit metadata changes: %s" , err )
}
return nil
}
2014-07-13 11:53:53 +02:00
// Deletes the medadata associated with this key
func ( f * FsDropbox ) deleteMetadata ( key string ) error {
return f . transaction ( func ( ) error {
record , err := f . table . Get ( key )
if err != nil {
return fmt . Errorf ( "Couldn't get record: %s" , err )
}
if record == nil {
return nil
}
record . DeleteRecord ( )
return nil
} )
}
2014-07-12 13:38:30 +02:00
// Reads the record attached to key
//
// Holds datastore mutex while in progress
func ( f * FsDropbox ) readRecord ( key string ) ( * dropbox . Record , error ) {
f . datastoreMutex . Lock ( )
defer f . datastoreMutex . Unlock ( )
if f . datastoreErr != nil {
return nil , f . datastoreErr
}
return f . table . Get ( key )
}
2014-07-08 22:59:30 +02:00
// ------------------------------------------------------------
// Return the parent Fs
func ( o * FsObjectDropbox ) Fs ( ) fs . Fs {
return o . dropbox
}
// Return a string version
func ( o * FsObjectDropbox ) String ( ) string {
if o == nil {
return "<nil>"
}
return o . remote
}
// Return the remote path
func ( o * FsObjectDropbox ) Remote ( ) string {
return o . remote
}
// Md5sum returns the Md5sum of an object returning a lowercase hex string
//
// FIXME has to download the file!
func ( o * FsObjectDropbox ) Md5sum ( ) ( string , error ) {
if o . md5sum != "" {
return o . md5sum , nil
}
2014-07-10 01:17:40 +02:00
err := o . readMetaData ( )
2014-07-08 22:59:30 +02:00
if err != nil {
2014-07-10 01:17:40 +02:00
fs . Log ( o , "Failed to read metadata: %s" , err )
return "" , fmt . Errorf ( "Failed to read metadata: %s" , err )
2014-07-08 22:59:30 +02:00
}
2014-07-10 01:17:40 +02:00
// For pre-existing files which have no md5sum can read it and set it?
// in, err := o.Open()
// if err != nil {
// return "", err
// }
// defer in.Close()
// hash := md5.New()
// _, err = io.Copy(hash, in)
// if err != nil {
// return "", err
// }
// o.md5sum = fmt.Sprintf("%x", hash.Sum(nil))
2014-07-08 22:59:30 +02:00
return o . md5sum , nil
}
// Size returns the size of an object in bytes
func ( o * FsObjectDropbox ) Size ( ) int64 {
return o . bytes
}
2014-07-10 01:17:40 +02:00
// setMetadataFromEntry sets the fs data from a dropbox.Entry
//
// This isn't a complete set of metadata and has an inacurate date
func ( o * FsObjectDropbox ) setMetadataFromEntry ( info * dropbox . Entry ) {
2015-08-03 22:18:34 +02:00
o . bytes = info . Bytes
2014-07-08 22:59:30 +02:00
o . modTime = time . Time ( info . ClientMtime )
}
2014-07-10 01:17:40 +02:00
// Reads the entry from dropbox
func ( o * FsObjectDropbox ) readEntry ( ) ( * dropbox . Entry , error ) {
entry , err := o . dropbox . db . Metadata ( o . remotePath ( ) , false , false , "" , "" , metadataLimit )
if err != nil {
fs . Debug ( o , "Error reading file: %s" , err )
return nil , fmt . Errorf ( "Error reading file: %s" , err )
}
return entry , nil
}
// Read entry if not set and set metadata from it
func ( o * FsObjectDropbox ) readEntryAndSetMetadata ( ) error {
// Last resort set time from client
if ! o . modTime . IsZero ( ) {
return nil
}
entry , err := o . readEntry ( )
if err != nil {
return err
}
o . setMetadataFromEntry ( entry )
return nil
}
2014-07-08 22:59:30 +02:00
// Returns the remote path for the object
func ( o * FsObjectDropbox ) remotePath ( ) string {
2014-07-12 12:46:45 +02:00
return o . dropbox . slashRootSlash + o . remote
2014-07-08 22:59:30 +02:00
}
2014-07-13 11:53:53 +02:00
// Returns the key for the metadata database for a given path
func metadataKey ( path string ) string {
// NB File system is case insensitive
path = strings . ToLower ( path )
2014-07-19 16:48:40 +02:00
hash := md5 . New ( )
2014-07-25 19:19:49 +02:00
_ , _ = hash . Write ( [ ] byte ( path ) )
2014-07-19 16:48:40 +02:00
return fmt . Sprintf ( "%x" , hash . Sum ( nil ) )
2014-07-13 11:53:53 +02:00
}
2014-07-10 01:17:40 +02:00
// Returns the key for the metadata database
func ( o * FsObjectDropbox ) metadataKey ( ) string {
2014-07-13 11:53:53 +02:00
return metadataKey ( o . remotePath ( ) )
2014-07-10 01:17:40 +02:00
}
2014-07-08 22:59:30 +02:00
// readMetaData gets the info if it hasn't already been fetched
func ( o * FsObjectDropbox ) readMetaData ( ) ( err error ) {
2014-07-10 01:17:40 +02:00
if o . md5sum != "" {
2014-07-08 22:59:30 +02:00
return nil
}
2014-07-10 01:17:40 +02:00
2014-07-12 12:46:45 +02:00
// fs.Debug(o, "Reading metadata from datastore")
2014-07-12 13:38:30 +02:00
record , err := o . dropbox . readRecord ( o . metadataKey ( ) )
2014-07-08 22:59:30 +02:00
if err != nil {
2014-07-10 01:17:40 +02:00
fs . Debug ( o , "Couldn't read metadata: %s" , err )
record = nil
2014-07-08 22:59:30 +02:00
}
2014-07-10 01:17:40 +02:00
if record != nil {
// Read md5sum
md5sumInterface , ok , err := record . Get ( md5sumField )
if err != nil {
return err
}
if ! ok {
fs . Debug ( o , "Couldn't find md5sum in record" )
} else {
md5sum , ok := md5sumInterface . ( string )
if ! ok {
fs . Debug ( o , "md5sum not a string" )
} else {
o . md5sum = md5sum
}
}
// read mtime
mtimeInterface , ok , err := record . Get ( mtimeField )
if err != nil {
return err
}
if ! ok {
fs . Debug ( o , "Couldn't find mtime in record" )
} else {
mtime , ok := mtimeInterface . ( string )
if ! ok {
fs . Debug ( o , "mtime not a string" )
} else {
2014-07-29 18:50:07 +02:00
modTime , err := time . Parse ( timeFormatIn , mtime )
2014-07-10 01:17:40 +02:00
if err != nil {
return err
}
o . modTime = modTime
}
}
}
// Last resort
2014-07-25 19:19:49 +02:00
return o . readEntryAndSetMetadata ( )
2014-07-08 22:59:30 +02:00
}
// ModTime returns the modification time of the object
//
// It attempts to read the objects mtime and if that isn't present the
// LastModified returned in the http headers
func ( o * FsObjectDropbox ) ModTime ( ) time . Time {
err := o . readMetaData ( )
if err != nil {
fs . Log ( o , "Failed to read metadata: %s" , err )
return time . Now ( )
}
return o . modTime
}
2014-07-10 01:17:40 +02:00
// Sets the modification time of the local fs object into the record
// FIXME if we don't set md5sum what will that do?
func ( o * FsObjectDropbox ) setModTimeAndMd5sum ( modTime time . Time , md5sum string ) error {
2014-07-11 18:21:23 +02:00
key := o . metadataKey ( )
2014-07-12 12:46:45 +02:00
// fs.Debug(o, "Writing metadata to datastore")
2014-07-11 18:21:23 +02:00
return o . dropbox . transaction ( func ( ) error {
record , err := o . dropbox . table . GetOrInsert ( key )
2014-07-10 01:17:40 +02:00
if err != nil {
2014-07-11 18:21:23 +02:00
return fmt . Errorf ( "Couldn't read record: %s" , err )
2014-07-10 01:17:40 +02:00
}
2014-07-11 18:21:23 +02:00
if md5sum != "" {
err = record . Set ( md5sumField , md5sum )
if err != nil {
return fmt . Errorf ( "Couldn't set md5sum record: %s" , err )
}
2014-07-20 12:23:05 +02:00
o . md5sum = md5sum
2014-07-10 01:17:40 +02:00
}
2014-07-11 18:21:23 +02:00
if ! modTime . IsZero ( ) {
2014-07-29 18:50:07 +02:00
mtime := modTime . Format ( timeFormatOut )
2014-07-11 18:21:23 +02:00
err := record . Set ( mtimeField , mtime )
if err != nil {
return fmt . Errorf ( "Couldn't set mtime record: %s" , err )
}
2014-07-20 12:23:05 +02:00
o . modTime = modTime
2014-07-11 18:21:23 +02:00
}
return nil
} )
2014-07-10 01:17:40 +02:00
}
2014-07-13 11:53:53 +02:00
// Deletes the medadata associated with this file
//
// It logs any errors
func ( o * FsObjectDropbox ) deleteMetadata ( ) {
fs . Debug ( o , "Deleting metadata from datastore" )
err := o . dropbox . deleteMetadata ( o . metadataKey ( ) )
if err != nil {
2015-08-08 21:10:31 +02:00
fs . ErrorLog ( o , "Error deleting metadata: %v" , err )
2014-07-13 11:53:53 +02:00
fs . Stats . Error ( )
}
}
2014-07-08 22:59:30 +02:00
// Sets the modification time of the local fs object
2014-07-10 01:17:40 +02:00
//
// Commits the datastore
2014-07-08 22:59:30 +02:00
func ( o * FsObjectDropbox ) SetModTime ( modTime time . Time ) {
2014-07-10 01:17:40 +02:00
err := o . setModTimeAndMd5sum ( modTime , "" )
2014-07-08 22:59:30 +02:00
if err != nil {
fs . Stats . Error ( )
2015-08-08 21:10:31 +02:00
fs . ErrorLog ( o , err . Error ( ) )
2014-07-08 22:59:30 +02:00
}
}
// Is this object storable
func ( o * FsObjectDropbox ) Storable ( ) bool {
return true
}
// Open an object for read
func ( o * FsObjectDropbox ) Open ( ) ( in io . ReadCloser , err error ) {
in , _ , err = o . dropbox . db . Download ( o . remotePath ( ) , "" , 0 )
return
}
// Update the already existing object
//
// Copy the reader into the object updating modTime and size
//
// The new object may have been created if an error is returned
func ( o * FsObjectDropbox ) Update ( in io . Reader , modTime time . Time , size int64 ) error {
2014-07-10 01:17:40 +02:00
// Calculate md5sum as we upload it
hash := md5 . New ( )
rc := & readCloser { in : io . TeeReader ( in , hash ) }
2014-07-08 22:59:30 +02:00
entry , err := o . dropbox . db . UploadByChunk ( rc , uploadChunkSize , o . remotePath ( ) , true , "" )
if err != nil {
return fmt . Errorf ( "Upload failed: %s" , err )
}
2014-07-10 01:17:40 +02:00
o . setMetadataFromEntry ( entry )
md5sum := fmt . Sprintf ( "%x" , hash . Sum ( nil ) )
return o . setModTimeAndMd5sum ( modTime , md5sum )
2014-07-08 22:59:30 +02:00
}
// Remove an object
func ( o * FsObjectDropbox ) Remove ( ) error {
2014-07-13 11:53:53 +02:00
o . deleteMetadata ( )
2014-07-08 22:59:30 +02:00
_ , err := o . dropbox . db . Delete ( o . remotePath ( ) )
return err
}
// Check the interfaces are satisfied
var _ fs . Fs = & FsDropbox { }
var _ fs . Purger = & FsDropbox { }
var _ fs . Object = & FsObjectDropbox { }