2015-09-22 19:47:16 +02:00
// Package dropbox provides an interface to Dropbox object storage
2014-07-08 22:59:30 +02:00
package dropbox
/ *
Limitations of dropbox
2014-07-12 12:46:45 +02:00
File system is case insensitive
2014-07-08 22:59:30 +02:00
* /
import (
"crypto/md5"
"fmt"
"io"
2015-08-17 00:24:34 +02:00
"io/ioutil"
2014-07-08 22:59:30 +02:00
"log"
2016-06-25 22:23:20 +02:00
"net/http"
2014-07-14 12:24:04 +02:00
"path"
2015-08-20 19:36:06 +02:00
"regexp"
2014-07-08 22:59:30 +02:00
"strings"
"time"
"github.com/ncw/rclone/fs"
2015-08-29 18:45:10 +02:00
"github.com/ncw/rclone/oauthutil"
2016-06-12 16:06:02 +02:00
"github.com/pkg/errors"
2014-07-08 22:59:30 +02:00
"github.com/stacktic/dropbox"
)
// Constants
const (
2016-02-28 20:57:19 +01:00
rcloneAppKey = "5jcck7diasz0rqy"
2016-08-14 13:04:43 +02:00
rcloneEncryptedAppSecret = "fRS5vVLr2v6FbyXYnIgjwBuUAt0osq_QZTXAEcmZ7g"
2016-02-28 20:57:19 +01:00
metadataLimit = dropbox . MetadataLimitDefault // max items to fetch at once
2014-07-08 22:59:30 +02:00
)
2015-08-25 20:01:37 +02:00
var (
// A regexp matching path names for files Dropbox ignores
// See https://www.dropbox.com/en/help/145 - Ignored files
ignoredFiles = regexp . MustCompile ( ` (?i)(^|/)(desktop\.ini|thumbs\.db|\.ds_store|icon\r|\.dropbox|\.dropbox.attr)$ ` )
// Upload chunk size - setting too small makes uploads slow.
// Chunks aren't buffered into memory though so can set large.
uploadChunkSize = fs . SizeSuffix ( 128 * 1024 * 1024 )
maxUploadChunkSize = fs . SizeSuffix ( 150 * 1024 * 1024 )
)
2015-08-20 19:36:06 +02:00
2014-07-08 22:59:30 +02:00
// Register with Fs
func init ( ) {
2016-02-18 12:35:25 +01:00
fs . Register ( & fs . RegInfo {
2016-02-15 19:11:53 +01:00
Name : "dropbox" ,
Description : "Dropbox" ,
NewFs : NewFs ,
Config : configHelper ,
2014-07-08 22:59:30 +02:00
Options : [ ] fs . Option { {
Name : "app_key" ,
2015-10-03 15:23:12 +02:00
Help : "Dropbox App Key - leave blank normally." ,
2014-07-08 22:59:30 +02:00
} , {
Name : "app_secret" ,
2015-10-03 15:23:12 +02:00
Help : "Dropbox App Secret - leave blank normally." ,
2014-07-08 22:59:30 +02:00
} } ,
} )
2016-12-20 16:50:46 +01:00
fs . VarP ( & uploadChunkSize , "dropbox-chunk-size" , "" , fmt . Sprintf ( "Upload chunk size. Max %v." , maxUploadChunkSize ) )
2014-07-08 22:59:30 +02:00
}
// 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
2016-12-20 19:03:09 +01:00
token := fs . ConfigFileGet ( name , "token" )
2014-07-08 22:59:30 +02:00
if token != "" {
fmt . Printf ( "Already have a dropbox token - refresh?\n" )
if ! fs . Confirm ( ) {
return
}
}
// Get a dropbox
2015-09-22 08:31:12 +02:00
db , err := newDropbox ( name )
if err != nil {
log . Fatalf ( "Failed to create dropbox client: %v" , err )
}
2014-07-08 22:59:30 +02:00
// 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
2016-12-20 19:03:09 +01:00
old := fs . ConfigFileGet ( name , "token" )
2014-07-08 22:59:30 +02:00
if token != old {
2016-12-20 19:03:09 +01:00
fs . ConfigFileSet ( name , "token" , token )
2014-07-08 22:59:30 +02:00
fs . SaveConfig ( )
}
}
2015-11-07 12:14:46 +01:00
// Fs represents a remote dropbox server
type Fs struct {
2015-08-22 17:53:11 +02:00
name string // name of this remote
2015-08-17 00:24:34 +02:00
root string // the path we are working on
2017-01-13 18:21:47 +01:00
features * fs . Features // optional features
db * dropbox . Dropbox // the connection to the dropbox server
2015-08-17 00:24:34 +02:00
slashRoot string // root with "/" prefix, lowercase
slashRootSlash string // root with "/" prefix and postfix, lowercase
2014-07-08 22:59:30 +02:00
}
2015-11-07 12:14:46 +01:00
// Object describes a dropbox object
type Object struct {
fs * Fs // what this object is part of
remote string // The remote path
bytes int64 // size of the object
modTime time . Time // time it was last modified
hasMetadata bool // metadata is valid
2016-09-21 23:13:24 +02:00
mimeType string // content type according to the server
2014-07-08 22:59:30 +02:00
}
// ------------------------------------------------------------
2015-09-22 19:47:16 +02:00
// Name of the remote (as passed into NewFs)
2015-11-07 12:14:46 +01:00
func ( f * Fs ) Name ( ) string {
2015-08-22 17:53:11 +02:00
return f . name
}
2015-09-22 19:47:16 +02:00
// Root of the remote (as passed into NewFs)
2015-11-07 12:14:46 +01:00
func ( f * Fs ) Root ( ) string {
2015-09-01 21:45:27 +02:00
return f . root
}
2015-11-07 12:14:46 +01:00
// String converts this Fs to a string
func ( f * Fs ) String ( ) string {
2014-07-08 22:59:30 +02:00
return fmt . Sprintf ( "Dropbox root '%s'" , f . root )
}
2017-01-13 18:21:47 +01:00
// Features returns the optional features of this Fs
func ( f * Fs ) Features ( ) * fs . Features {
return f . features
}
2014-07-08 22:59:30 +02:00
// Makes a new dropbox from the config
2015-09-22 08:31:12 +02:00
func newDropbox ( name string ) ( * dropbox . Dropbox , error ) {
2014-07-08 22:59:30 +02:00
db := dropbox . NewDropbox ( )
2016-12-20 19:03:09 +01:00
appKey := fs . ConfigFileGet ( name , "app_key" )
2014-07-08 22:59:30 +02:00
if appKey == "" {
appKey = rcloneAppKey
}
2016-12-20 19:03:09 +01:00
appSecret := fs . ConfigFileGet ( name , "app_secret" )
2014-07-08 22:59:30 +02:00
if appSecret == "" {
2016-08-14 13:04:43 +02:00
appSecret = fs . MustReveal ( rcloneEncryptedAppSecret )
2014-07-08 22:59:30 +02:00
}
2015-09-22 08:31:12 +02:00
err := db . SetAppInfo ( appKey , appSecret )
return db , err
2014-07-08 22:59:30 +02:00
}
2015-11-07 12:14:46 +01:00
// NewFs contstructs an Fs from the path, container:path
2014-07-14 12:24:04 +02:00
func NewFs ( name , root string ) ( fs . Fs , error ) {
2015-08-25 20:01:37 +02:00
if uploadChunkSize > maxUploadChunkSize {
2016-06-12 16:06:02 +02:00
return nil , errors . Errorf ( "chunk size too big, must be < %v" , maxUploadChunkSize )
2015-08-25 20:01:37 +02:00
}
2015-09-22 08:31:12 +02:00
db , err := newDropbox ( name )
if err != nil {
return nil , err
}
2015-11-07 12:14:46 +01:00
f := & Fs {
2015-08-22 17:53:11 +02:00
name : name ,
db : db ,
2014-07-08 22:59:30 +02:00
}
2017-01-13 18:21:47 +01:00
f . features = ( & fs . Features { CaseInsensitive : true , ReadMimeType : true } ) . Fill ( f )
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
2016-12-20 19:03:09 +01:00
token := fs . ConfigFileGet ( name , "token" )
2014-07-08 22:59:30 +02:00
2015-08-29 18:45:10 +02:00
// Set our custom context which enables our custom transport for timeouts etc
db . SetContext ( oauthutil . Context ( ) )
2014-07-08 22:59:30 +02:00
// Authorize the client
db . SetAccessToken ( token )
2014-07-14 12:24:04 +02:00
// See if the root is actually an object
entry , err := f . db . Metadata ( f . slashRoot , false , false , "" , "" , metadataLimit )
if err == nil && ! entry . IsDir {
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 )
2016-06-21 19:01:53 +02:00
// return an error with an fs which points to the parent
return f , fs . ErrorIsFile
2014-07-14 12:24:04 +02:00
}
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
2015-11-07 12:14:46 +01:00
func ( f * Fs ) setRoot ( root string ) {
2014-07-14 12:24:04 +02:00
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 += "/"
}
}
2016-06-25 22:58:34 +02:00
// Return an Object from a path
2014-07-29 18:50:07 +02:00
//
2016-06-25 22:23:20 +02:00
// If it can't be found it returns the error fs.ErrorObjectNotFound.
2017-02-25 16:23:27 +01:00
func ( f * Fs ) newObjectWithInfo ( remote string , info * dropbox . Entry ) ( fs . Object , error ) {
o := & Object {
2015-11-07 12:14:46 +01:00
fs : f ,
remote : remote ,
2014-07-08 22:59:30 +02:00
}
2017-02-25 16:23:27 +01:00
var err error
2014-07-12 12:46:45 +02:00
if info != nil {
2017-02-25 12:09:57 +01:00
err = o . setMetadataFromEntry ( info )
2014-07-08 22:59:30 +02:00
} else {
2017-02-25 12:09:57 +01:00
err = o . readEntryAndSetMetadata ( )
}
if err != nil {
return nil , err
2014-07-08 22:59:30 +02:00
}
2016-06-25 22:23:20 +02:00
return o , nil
2014-07-08 22:59:30 +02:00
}
2016-06-25 22:23:20 +02:00
// NewObject finds the Object at remote. If it can't be found
// it returns the error fs.ErrorObjectNotFound.
func ( f * Fs ) NewObject ( remote string ) ( fs . Object , error ) {
2016-06-25 22:58:34 +02:00
return f . newObjectWithInfo ( 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
2016-05-07 15:50:35 +02:00
func strip ( path , root string ) ( string , error ) {
if len ( root ) > 0 {
if root [ 0 ] != '/' {
root = "/" + root
}
if root [ len ( root ) - 1 ] != '/' {
root += "/"
}
2016-05-16 18:54:59 +02:00
} else if len ( root ) == 0 {
root = "/"
2016-05-07 15:50:35 +02:00
}
2017-02-22 13:48:16 +01:00
if ! strings . HasPrefix ( strings . ToLower ( path ) , strings . ToLower ( root ) ) {
2016-06-12 16:06:02 +02:00
return "" , errors . Errorf ( "path %q is not under root %q" , path , root )
2014-07-08 22:59:30 +02:00
}
2016-05-07 15:50:35 +02:00
return path [ len ( root ) : ] , nil
}
// Strips the root off path and returns it
func ( f * Fs ) stripRoot ( path string ) ( string , error ) {
return strip ( path , f . slashRootSlash )
2014-07-08 22:59:30 +02:00
}
2016-06-25 22:58:34 +02:00
// Walk the root returning a channel of Objects
2016-04-23 22:46:52 +02:00
func ( f * Fs ) list ( out fs . ListOpts , dir string ) {
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
2015-09-22 19:47:16 +02:00
nameTree := newNameTree ( )
2014-07-12 12:46:45 +02:00
cursor := ""
2016-04-23 22:46:52 +02:00
root := f . slashRoot
if dir != "" {
root += "/" + dir
// We assume that dir is entered in the correct case
// here which is likely since it probably came from a
// directory listing
nameTree . PutCaseCorrectPath ( strings . Trim ( root , "/" ) )
}
2014-07-12 12:46:45 +02:00
for {
2016-04-23 22:46:52 +02:00
deltaPage , err := f . db . Delta ( cursor , root )
2014-07-12 12:46:45 +02:00
if err != nil {
2016-06-12 16:06:02 +02:00
out . SetError ( errors . Wrap ( err , "couldn't list" ) )
2016-05-07 15:50:35 +02:00
return
}
if deltaPage . Reset && cursor != "" {
2016-06-12 16:06:02 +02:00
err = errors . New ( "unexpected reset during listing" )
2016-05-30 20:49:21 +02:00
out . SetError ( err )
2014-07-12 12:46:45 +02:00
break
2016-05-07 15:50:35 +02:00
}
2017-02-09 12:01:20 +01:00
fs . Debugf ( f , "%d delta entries received" , len ( deltaPage . Entries ) )
2016-05-07 15:50:35 +02:00
for i := range deltaPage . Entries {
deltaEntry := & deltaPage . Entries [ i ]
entry := deltaEntry . Entry
if entry == nil {
// This notifies of a deleted object
} else {
if len ( entry . Path ) <= 1 || entry . Path [ 0 ] != '/' {
2017-02-09 18:08:51 +01:00
fs . Debugf ( f , "dropbox API inconsistency: a path should always start with a slash and be at least 2 characters: %s" , entry . Path )
2016-05-07 15:50:35 +02:00
continue
}
2015-05-23 20:56:48 +02:00
2016-05-07 15:50:35 +02:00
lastSlashIndex := strings . LastIndex ( entry . Path , "/" )
2015-05-23 20:56:48 +02:00
2016-05-07 15:50:35 +02:00
var parentPath string
if lastSlashIndex == 0 {
parentPath = ""
} else {
parentPath = entry . Path [ 1 : lastSlashIndex ]
}
lastComponent := entry . Path [ lastSlashIndex + 1 : ]
2015-05-23 20:56:48 +02:00
2016-05-07 15:50:35 +02:00
if entry . IsDir {
nameTree . PutCaseCorrectDirectoryName ( parentPath , lastComponent )
name , err := f . stripRoot ( entry . Path + "/" )
if err != nil {
out . SetError ( err )
return
}
name = strings . Trim ( name , "/" )
if name != "" && name != dir {
dir := & fs . Dir {
Name : name ,
When : time . Time ( entry . ClientMtime ) ,
Bytes : entry . Bytes ,
Count : - 1 ,
}
if out . AddDir ( dir ) {
return
}
}
} else {
parentPathCorrectCase := nameTree . GetPathWithCorrectCase ( parentPath )
if parentPathCorrectCase != nil {
path , err := f . stripRoot ( * parentPathCorrectCase + "/" + lastComponent )
2016-04-21 21:06:21 +02:00
if err != nil {
out . SetError ( err )
return
}
2016-06-25 22:23:20 +02:00
o , err := f . newObjectWithInfo ( path , entry )
if err != nil {
out . SetError ( err )
return
}
if out . Add ( o ) {
return
2016-04-21 21:06:21 +02:00
}
2014-07-13 11:53:53 +02:00
} else {
2016-05-07 15:50:35 +02:00
nameTree . PutFile ( parentPath , lastComponent , entry )
2014-07-13 11:53:53 +02:00
}
2014-07-12 12:46:45 +02:00
}
}
2014-07-08 22:59:30 +02:00
}
2016-05-07 15:50:35 +02:00
if ! deltaPage . HasMore {
break
}
cursor = deltaPage . Cursor . Cursor
2014-07-08 22:59:30 +02:00
}
2015-05-23 20:56:48 +02:00
2016-04-21 21:06:21 +02:00
walkFunc := func ( caseCorrectFilePath string , entry * dropbox . Entry ) error {
path , err := f . stripRoot ( "/" + caseCorrectFilePath )
if err != nil {
return err
2015-05-23 20:56:48 +02:00
}
2016-06-25 22:23:20 +02:00
o , err := f . newObjectWithInfo ( path , entry )
if err != nil {
return err
}
if out . Add ( o ) {
return fs . ErrorListAborted
2016-04-21 21:06:21 +02:00
}
return nil
}
err := nameTree . WalkFiles ( f . root , walkFunc )
if err != nil {
out . SetError ( err )
2015-05-23 20:56:48 +02:00
}
2014-07-08 22:59:30 +02:00
}
2016-05-07 15:50:35 +02:00
// listOneLevel walks the path one level deep
func ( f * Fs ) listOneLevel ( out fs . ListOpts , dir string ) {
root := f . root
if dir != "" {
root += "/" + dir
}
2016-06-21 22:17:52 +02:00
dirEntry , err := f . db . Metadata ( root , true , false , "" , "" , metadataLimit )
2016-05-07 15:50:35 +02:00
if err != nil {
2016-06-12 16:06:02 +02:00
out . SetError ( errors . Wrap ( err , "couldn't list single level" ) )
2016-05-07 15:50:35 +02:00
return
}
2016-06-21 22:17:52 +02:00
for i := range dirEntry . Contents {
entry := & dirEntry . Contents [ i ]
2017-02-24 23:49:29 +01:00
// Normalise the path to the dir passed in
remote := path . Join ( dir , path . Base ( entry . Path ) )
2016-05-07 15:50:35 +02:00
if entry . IsDir {
dir := & fs . Dir {
Name : remote ,
When : time . Time ( entry . ClientMtime ) ,
Bytes : entry . Bytes ,
Count : - 1 ,
}
if out . AddDir ( dir ) {
return
}
2014-07-08 22:59:30 +02:00
} else {
2016-06-25 22:23:20 +02:00
o , err := f . newObjectWithInfo ( remote , entry )
if err != nil {
out . SetError ( err )
return
}
if out . Add ( o ) {
return
2014-07-08 22:59:30 +02:00
}
}
2016-05-07 15:50:35 +02:00
}
}
2016-06-25 22:58:34 +02:00
// List walks the path returning a channel of Objects
2016-05-07 15:50:35 +02:00
func ( f * Fs ) List ( out fs . ListOpts , dir string ) {
defer out . Finished ( )
level := out . Level ( )
switch level {
case 1 :
f . listOneLevel ( out , dir )
case fs . MaxLevel :
f . list ( out , dir )
default :
out . SetError ( fs . ErrorLevelNotSupported )
}
2014-07-08 22:59:30 +02:00
}
// 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
2016-02-18 12:35:25 +01:00
func ( f * Fs ) Put ( in io . Reader , src fs . ObjectInfo ) ( fs . Object , error ) {
2015-11-07 12:14:46 +01:00
// Temporary Object under construction
o := & Object {
fs : f ,
2016-02-18 12:35:25 +01:00
remote : src . Remote ( ) ,
2015-11-07 12:14:46 +01:00
}
2016-02-18 12:35:25 +01:00
return o , o . Update ( in , src )
2014-07-08 22:59:30 +02:00
}
// Mkdir creates the container if it doesn't exist
2016-11-25 22:52:43 +01:00
func ( f * Fs ) Mkdir ( dir string ) error {
root := path . Join ( f . slashRoot , dir )
entry , err := f . db . Metadata ( root , false , false , "" , "" , metadataLimit )
2014-07-13 11:51:47 +02:00
if err == nil {
if entry . IsDir {
return nil
}
2016-06-12 16:06:02 +02:00
return errors . Errorf ( "%q already exists as file" , f . root )
2014-07-13 11:51:47 +02:00
}
2016-11-25 22:52:43 +01:00
_ , err = f . db . CreateFolder ( root )
2014-07-08 22:59:30 +02:00
return err
}
// Rmdir deletes the container
//
// Returns an error if it isn't empty
2016-11-25 22:52:43 +01:00
func ( f * Fs ) Rmdir ( dir string ) error {
root := path . Join ( f . slashRoot , dir )
entry , err := f . db . Metadata ( root , true , false , "" , "" , 16 )
2014-07-08 22:59:30 +02:00
if err != nil {
return err
}
if len ( entry . Contents ) != 0 {
2016-06-12 16:06:02 +02:00
return errors . New ( "directory not empty" )
2014-07-08 22:59:30 +02:00
}
2016-11-25 22:52:43 +01:00
_ , err = f . db . Delete ( root )
return err
2014-07-08 22:59:30 +02:00
}
2015-09-22 19:47:16 +02:00
// Precision returns the precision
2015-11-07 12:14:46 +01:00
func ( f * Fs ) Precision ( ) time . Duration {
2015-08-17 00:24:34 +02:00
return fs . ModTimeNotSupported
2014-07-08 22:59:30 +02:00
}
2015-02-14 19:48:08 +01:00
// Copy src to this remote using server side copy operations.
//
// This is stored with the remote path given
//
// It returns the destination Object and a possible error
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantCopy
2015-11-07 12:14:46 +01:00
func ( f * Fs ) Copy ( src fs . Object , remote string ) ( fs . Object , error ) {
srcObj , ok := src . ( * Object )
2015-02-14 19:48:08 +01:00
if ! ok {
2017-02-09 12:01:20 +01:00
fs . Debugf ( src , "Can't copy - not same remote type" )
2015-02-14 19:48:08 +01:00
return nil , fs . ErrorCantCopy
}
2015-11-07 12:14:46 +01:00
// Temporary Object under construction
dstObj := & Object {
fs : f ,
remote : remote ,
}
2015-02-14 19:48:08 +01:00
srcPath := srcObj . remotePath ( )
dstPath := dstObj . remotePath ( )
entry , err := f . db . Copy ( srcPath , dstPath , false )
if err != nil {
2016-06-12 16:06:02 +02:00
return nil , errors . Wrap ( err , "copy failed" )
2015-02-14 19:48:08 +01:00
}
2017-02-25 12:09:57 +01:00
err = dstObj . setMetadataFromEntry ( entry )
if err != nil {
return nil , errors . Wrap ( err , "copy failed" )
}
2015-02-14 19:48:08 +01:00
return dstObj , nil
}
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()
2015-11-07 12:14:46 +01:00
func ( f * Fs ) Purge ( ) error {
2014-07-13 11:53:53 +02:00
// Let dropbox delete the filesystem tree
2014-07-08 22:59:30 +02:00
_ , err := f . db . Delete ( f . slashRoot )
return err
}
2015-08-31 22:05:51 +02:00
// Move src to this remote using server side move operations.
//
// This is stored with the remote path given
//
// It returns the destination Object and a possible error
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantMove
2015-11-07 12:14:46 +01:00
func ( f * Fs ) Move ( src fs . Object , remote string ) ( fs . Object , error ) {
srcObj , ok := src . ( * Object )
2015-08-31 22:05:51 +02:00
if ! ok {
2017-02-09 12:01:20 +01:00
fs . Debugf ( src , "Can't move - not same remote type" )
2015-08-31 22:05:51 +02:00
return nil , fs . ErrorCantMove
}
2015-11-07 12:14:46 +01:00
// Temporary Object under construction
dstObj := & Object {
fs : f ,
remote : remote ,
}
2015-08-31 22:05:51 +02:00
srcPath := srcObj . remotePath ( )
dstPath := dstObj . remotePath ( )
2015-09-22 19:47:16 +02:00
entry , err := f . db . Move ( srcPath , dstPath )
2015-08-31 22:05:51 +02:00
if err != nil {
2016-06-12 16:06:02 +02:00
return nil , errors . Wrap ( err , "move failed" )
2015-08-31 22:05:51 +02:00
}
2017-02-25 12:09:57 +01:00
err = dstObj . setMetadataFromEntry ( entry )
if err != nil {
return nil , errors . Wrap ( err , "move failed" )
}
2015-08-31 22:05:51 +02:00
return dstObj , nil
}
2017-02-05 22:20:56 +01:00
// DirMove moves src, srcRemote to this remote at dstRemote
// using server side move operations.
2015-08-31 22:05:51 +02:00
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantDirMove
//
// If destination exists then return fs.ErrorDirExists
2017-02-05 22:20:56 +01:00
func ( f * Fs ) DirMove ( src fs . Fs , srcRemote , dstRemote string ) error {
2015-11-07 12:14:46 +01:00
srcFs , ok := src . ( * Fs )
2015-08-31 22:05:51 +02:00
if ! ok {
2017-02-09 12:01:20 +01:00
fs . Debugf ( srcFs , "Can't move directory - not same remote type" )
2015-08-31 22:05:51 +02:00
return fs . ErrorCantDirMove
}
2017-02-05 22:20:56 +01:00
srcPath := path . Join ( srcFs . slashRoot , srcRemote )
dstPath := path . Join ( f . slashRoot , dstRemote )
2015-08-31 22:05:51 +02:00
// Check if destination exists
2015-09-22 19:47:16 +02:00
entry , err := f . db . Metadata ( f . slashRoot , false , false , "" , "" , metadataLimit )
2015-08-31 22:05:51 +02:00
if err == nil && ! entry . IsDeleted {
return fs . ErrorDirExists
}
2017-02-05 22:20:56 +01:00
// Make sure the parent directory exists
// ...apparently not necessary
2015-08-31 22:05:51 +02:00
// Do the move
2017-02-05 22:20:56 +01:00
_ , err = f . db . Move ( srcPath , dstPath )
2015-08-31 22:05:51 +02:00
if err != nil {
2016-06-12 16:06:02 +02:00
return errors . Wrap ( err , "MoveDir failed" )
2015-08-31 22:05:51 +02:00
}
return nil
}
2016-01-11 13:39:33 +01:00
// Hashes returns the supported hash sets.
func ( f * Fs ) Hashes ( ) fs . HashSet {
return fs . HashSet ( fs . HashNone )
}
2014-07-08 22:59:30 +02:00
// ------------------------------------------------------------
2015-09-22 19:47:16 +02:00
// Fs returns the parent Fs
2016-02-18 12:35:25 +01:00
func ( o * Object ) Fs ( ) fs . Info {
2015-11-07 12:14:46 +01:00
return o . fs
2014-07-08 22:59:30 +02:00
}
// Return a string version
2015-11-07 12:14:46 +01:00
func ( o * Object ) String ( ) string {
2014-07-08 22:59:30 +02:00
if o == nil {
return "<nil>"
}
return o . remote
}
2015-09-22 19:47:16 +02:00
// Remote returns the remote path
2015-11-07 12:14:46 +01:00
func ( o * Object ) Remote ( ) string {
2014-07-08 22:59:30 +02:00
return o . remote
}
2016-01-11 13:39:33 +01:00
// Hash is unsupported on Dropbox
func ( o * Object ) Hash ( t fs . HashType ) ( string , error ) {
return "" , fs . ErrHashUnsupported
2014-07-08 22:59:30 +02:00
}
// Size returns the size of an object in bytes
2015-11-07 12:14:46 +01:00
func ( o * Object ) Size ( ) int64 {
2014-07-08 22:59:30 +02:00
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
2017-02-25 12:09:57 +01:00
func ( o * Object ) setMetadataFromEntry ( info * dropbox . Entry ) error {
if info . IsDir {
return errors . Wrapf ( fs . ErrorNotAFile , "%q" , o . remote )
}
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 )
2016-09-21 23:13:24 +02:00
o . mimeType = info . MimeType
2015-08-17 00:24:34 +02:00
o . hasMetadata = true
2017-02-25 12:09:57 +01:00
return nil
2014-07-08 22:59:30 +02:00
}
2014-07-10 01:17:40 +02:00
// Reads the entry from dropbox
2015-11-07 12:14:46 +01:00
func ( o * Object ) readEntry ( ) ( * dropbox . Entry , error ) {
entry , err := o . fs . db . Metadata ( o . remotePath ( ) , false , false , "" , "" , metadataLimit )
2014-07-10 01:17:40 +02:00
if err != nil {
2016-06-25 22:23:20 +02:00
if dropboxErr , ok := err . ( * dropbox . Error ) ; ok {
if dropboxErr . StatusCode == http . StatusNotFound {
return nil , fs . ErrorObjectNotFound
}
}
return nil , err
2014-07-10 01:17:40 +02:00
}
return entry , nil
}
// Read entry if not set and set metadata from it
2015-11-07 12:14:46 +01:00
func ( o * Object ) readEntryAndSetMetadata ( ) error {
2014-07-10 01:17:40 +02:00
// Last resort set time from client
if ! o . modTime . IsZero ( ) {
return nil
}
entry , err := o . readEntry ( )
if err != nil {
return err
}
2017-02-25 12:09:57 +01:00
return o . setMetadataFromEntry ( entry )
2014-07-10 01:17:40 +02:00
}
2014-07-08 22:59:30 +02:00
// Returns the remote path for the object
2015-11-07 12:14:46 +01:00
func ( o * Object ) remotePath ( ) string {
return o . fs . 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
2015-11-07 12:14:46 +01:00
func ( o * Object ) 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
2015-11-07 12:14:46 +01:00
func ( o * Object ) readMetaData ( ) ( err error ) {
2015-08-17 00:24:34 +02:00
if o . hasMetadata {
2014-07-08 22:59:30 +02:00
return nil
}
2014-07-10 01:17:40 +02:00
// 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
2015-11-07 12:14:46 +01:00
func ( o * Object ) ModTime ( ) time . Time {
2014-07-08 22:59:30 +02:00
err := o . readMetaData ( )
if err != nil {
2017-02-09 18:08:51 +01:00
fs . Debugf ( o , "Failed to read metadata: %v" , err )
2014-07-08 22:59:30 +02:00
return time . Now ( )
}
return o . modTime
}
2015-09-22 19:47:16 +02:00
// SetModTime sets the modification time of the local fs object
2014-07-10 01:17:40 +02:00
//
// Commits the datastore
2016-03-22 16:07:10 +01:00
func ( o * Object ) SetModTime ( modTime time . Time ) error {
2015-08-17 00:24:34 +02:00
// FIXME not implemented
2016-03-22 16:07:10 +01:00
return fs . ErrorCantSetModTime
2014-07-08 22:59:30 +02:00
}
2015-09-22 19:47:16 +02:00
// Storable returns whether this object is storable
2015-11-07 12:14:46 +01:00
func ( o * Object ) Storable ( ) bool {
2014-07-08 22:59:30 +02:00
return true
}
// Open an object for read
2016-09-10 12:29:57 +02:00
func ( o * Object ) Open ( options ... fs . OpenOption ) ( in io . ReadCloser , err error ) {
// FIXME should send a patch for dropbox module which allow setting headers
var offset int64
for _ , option := range options {
switch x := option . ( type ) {
case * fs . SeekOption :
offset = x . Offset
default :
if option . Mandatory ( ) {
2017-02-09 12:01:20 +01:00
fs . Logf ( o , "Unsupported mandatory option: %v" , option )
2016-09-10 12:29:57 +02:00
}
}
}
in , _ , err = o . fs . db . Download ( o . remotePath ( ) , "" , offset )
2016-07-04 14:45:10 +02:00
if dropboxErr , ok := err . ( * dropbox . Error ) ; ok {
// Dropbox return 461 for copyright violation so don't
// attempt to retry this error
if dropboxErr . StatusCode == 461 {
return nil , fs . NoRetryError ( err )
}
}
2014-07-08 22:59:30 +02:00
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
2016-02-18 12:35:25 +01:00
func ( o * Object ) Update ( in io . Reader , src fs . ObjectInfo ) error {
2015-08-20 19:36:06 +02:00
remote := o . remotePath ( )
if ignoredFiles . MatchString ( remote ) {
2017-02-09 12:01:20 +01:00
fs . Logf ( o , "File name disallowed - not uploading" )
2015-08-20 19:36:06 +02:00
return nil
}
2015-11-07 12:14:46 +01:00
entry , err := o . fs . db . UploadByChunk ( ioutil . NopCloser ( in ) , int ( uploadChunkSize ) , remote , true , "" )
2014-07-08 22:59:30 +02:00
if err != nil {
2016-06-12 16:06:02 +02:00
return errors . Wrap ( err , "upload failed" )
2014-07-08 22:59:30 +02:00
}
2017-02-25 12:09:57 +01:00
return o . setMetadataFromEntry ( entry )
2014-07-08 22:59:30 +02:00
}
// Remove an object
2015-11-07 12:14:46 +01:00
func ( o * Object ) Remove ( ) error {
_ , err := o . fs . db . Delete ( o . remotePath ( ) )
2014-07-08 22:59:30 +02:00
return err
}
2016-09-21 23:13:24 +02:00
// MimeType of an Object if known, "" otherwise
func ( o * Object ) MimeType ( ) string {
err := o . readMetaData ( )
if err != nil {
2017-02-09 12:01:20 +01:00
fs . Logf ( o , "Failed to read metadata: %v" , err )
2016-09-21 23:13:24 +02:00
return ""
}
return o . mimeType
}
2014-07-08 22:59:30 +02:00
// Check the interfaces are satisfied
2015-08-31 22:05:51 +02:00
var (
2016-09-21 23:13:24 +02:00
_ fs . Fs = ( * Fs ) ( nil )
_ fs . Copier = ( * Fs ) ( nil )
_ fs . Purger = ( * Fs ) ( nil )
_ fs . Mover = ( * Fs ) ( nil )
_ fs . DirMover = ( * Fs ) ( nil )
_ fs . Object = ( * Object ) ( nil )
_ fs . MimeTyper = ( * Object ) ( nil )
2015-08-31 22:05:51 +02:00
)