2018-10-26 21:51:28 +08:00
package operations
import (
2019-06-17 16:34:30 +08:00
"context"
2020-06-12 14:04:30 +08:00
"io"
"mime"
"mime/multipart"
"net/http"
2020-08-11 00:09:46 +08:00
"path"
2018-10-26 21:51:28 +08:00
"strings"
2020-06-12 14:04:30 +08:00
"time"
2018-10-26 21:51:28 +08:00
"github.com/pkg/errors"
2019-07-29 01:47:38 +08:00
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/rc"
2018-10-26 21:51:28 +08:00
)
func init ( ) {
rc . Add ( rc . Call {
Path : "operations/list" ,
AuthRequired : true ,
Fn : rcList ,
Title : "List the given remote and path in JSON format" ,
Help : ` This takes the following parameters
2020-10-14 05:49:58 +08:00
- fs - a remote name string e . g . "drive:"
- remote - a path within that remote e . g . "dir"
2018-10-26 21:51:28 +08:00
- opt - a dictionary of options to control the listing ( optional )
- recurse - If set recurse directories
- noModTime - If set return modification time
- showEncrypted - If set show decrypted names
- showOrigIDs - If set show the IDs for each item if known
- showHash - If set return a dictionary of hashes
2020-09-24 00:20:28 +08:00
- noMimeType - If set don ' t show mime types
- dirsOnly - If set only show directories
- filesOnly - If set only show files
- hashTypes - array of strings of hash types to show if showHash set
2018-10-26 21:51:28 +08:00
The result is
- list
- This is an array of objects as described in the lsjson command
2019-06-08 00:28:15 +08:00
See the [ lsjson command ] ( / commands / rclone_lsjson / ) for more information on the above and examples .
2018-10-26 21:51:28 +08:00
` ,
} )
}
// List the directory
2019-06-17 16:34:30 +08:00
func rcList ( ctx context . Context , in rc . Params ) ( out rc . Params , err error ) {
2020-11-05 23:18:51 +08:00
f , remote , err := rc . GetFsAndRemote ( ctx , in )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , err
}
var opt ListJSONOpt
err = in . GetStruct ( "opt" , & opt )
if rc . NotErrParamNotFound ( err ) {
return nil , err
}
var list = [ ] * ListJSONItem { }
2019-06-17 16:34:30 +08:00
err = ListJSON ( ctx , f , remote , & opt , func ( item * ListJSONItem ) error {
2018-10-26 21:51:28 +08:00
list = append ( list , item )
return nil
} )
if err != nil {
return nil , err
}
out = make ( rc . Params )
out [ "list" ] = list
return out , nil
}
2020-09-24 00:20:28 +08:00
func init ( ) {
rc . Add ( rc . Call {
Path : "operations/stat" ,
AuthRequired : true ,
Fn : rcStat ,
Title : "Give information about the supplied file or directory" ,
Help : ` This takes the following parameters
- fs - a remote name string eg "drive:"
- remote - a path within that remote eg "dir"
- opt - a dictionary of options to control the listing ( optional )
- see operations / list for the options
The result is
- item - an object as described in the lsjson command . Will be null if not found .
Note that if you are only interested in files then it is much more
efficient to set the filesOnly flag in the options .
See the [ lsjson command ] ( / commands / rclone_lsjson / ) for more information on the above and examples .
` ,
} )
}
// List the directory
func rcStat ( ctx context . Context , in rc . Params ) ( out rc . Params , err error ) {
f , remote , err := rc . GetFsAndRemote ( ctx , in )
if err != nil {
return nil , err
}
var opt ListJSONOpt
err = in . GetStruct ( "opt" , & opt )
if rc . NotErrParamNotFound ( err ) {
return nil , err
}
item , err := StatJSON ( ctx , f , remote , & opt )
if err != nil {
return nil , err
}
out = make ( rc . Params )
out [ "item" ] = item
return out , nil
}
2018-10-26 21:51:28 +08:00
func init ( ) {
rc . Add ( rc . Call {
Path : "operations/about" ,
AuthRequired : true ,
Fn : rcAbout ,
Title : "Return the space used on the remote" ,
Help : ` This takes the following parameters
2020-10-14 05:49:58 +08:00
- fs - a remote name string e . g . "drive:"
2018-10-26 21:51:28 +08:00
The result is as returned from rclone about -- json
2019-06-08 16:19:07 +08:00
See the [ about command ] ( / commands / rclone_size / ) command for more information on the above .
2018-10-26 21:51:28 +08:00
` ,
} )
}
// About the remote
2019-06-17 16:34:30 +08:00
func rcAbout ( ctx context . Context , in rc . Params ) ( out rc . Params , err error ) {
2020-11-05 23:18:51 +08:00
f , err := rc . GetFs ( ctx , in )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , err
}
doAbout := f . Features ( ) . About
if doAbout == nil {
return nil , errors . Errorf ( "%v doesn't support about" , f )
}
2019-06-17 16:34:30 +08:00
u , err := doAbout ( ctx )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , errors . Wrap ( err , "about call failed" )
}
err = rc . Reshape ( & out , u )
if err != nil {
return nil , errors . Wrap ( err , "about Reshape failed" )
}
return out , nil
}
func init ( ) {
for _ , copy := range [ ] bool { false , true } {
copy := copy
name := "Move"
if copy {
name = "Copy"
}
rc . Add ( rc . Call {
Path : "operations/" + strings . ToLower ( name ) + "file" ,
AuthRequired : true ,
2019-06-17 16:34:30 +08:00
Fn : func ( ctx context . Context , in rc . Params ) ( rc . Params , error ) {
return rcMoveOrCopyFile ( ctx , in , copy )
2018-10-26 21:51:28 +08:00
} ,
Title : name + " a file from source remote to destination remote" ,
Help : ` This takes the following parameters
2020-10-14 05:49:58 +08:00
- srcFs - a remote name string e . g . "drive:" for the source
- srcRemote - a path within that remote e . g . "file.txt" for the source
- dstFs - a remote name string e . g . "drive2:" for the destination
- dstRemote - a path within that remote e . g . "file2.txt" for the destination
2018-10-26 21:51:28 +08:00
` ,
} )
}
}
// Copy a file
2019-06-17 16:34:30 +08:00
func rcMoveOrCopyFile ( ctx context . Context , in rc . Params , cp bool ) ( out rc . Params , err error ) {
2020-11-05 23:18:51 +08:00
srcFs , srcRemote , err := rc . GetFsAndRemoteNamed ( ctx , in , "srcFs" , "srcRemote" )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , err
}
2020-11-05 23:18:51 +08:00
dstFs , dstRemote , err := rc . GetFsAndRemoteNamed ( ctx , in , "dstFs" , "dstRemote" )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , err
}
2019-06-17 16:34:30 +08:00
return nil , moveOrCopyFile ( ctx , dstFs , srcFs , dstRemote , srcRemote , cp )
2018-10-26 21:51:28 +08:00
}
func init ( ) {
for _ , op := range [ ] struct {
2020-06-05 22:26:46 +08:00
name string
title string
help string
noRemote bool
needsRequest bool
2018-10-26 21:51:28 +08:00
} {
{ name : "mkdir" , title : "Make a destination directory or container" } ,
{ name : "rmdir" , title : "Remove an empty directory or container" } ,
{ name : "purge" , title : "Remove a directory or container and all of its contents" } ,
{ name : "rmdirs" , title : "Remove all the empty directories in the path" , help : "- leaveRoot - boolean, set to true not to delete the root\n" } ,
{ name : "delete" , title : "Remove files in the path" , noRemote : true } ,
{ name : "deletefile" , title : "Remove the single file pointed to" } ,
2019-09-04 00:25:19 +08:00
{ name : "copyurl" , title : "Copy the URL to the object" , help : "- url - string, URL to read from\n - autoFilename - boolean, set to true to retrieve destination file name from url" } ,
2020-06-12 14:04:30 +08:00
{ name : "uploadfile" , title : "Upload file using multiform/form-data" , help : "- each part in body represents a file to be uploaded" , needsRequest : true } ,
2018-10-26 21:51:28 +08:00
{ name : "cleanup" , title : "Remove trashed files in the remote or path" , noRemote : true } ,
} {
op := op
2020-10-14 05:49:58 +08:00
remote := "- remote - a path within that remote e.g. \"dir\"\n"
2018-10-26 21:51:28 +08:00
if op . noRemote {
remote = ""
}
rc . Add ( rc . Call {
Path : "operations/" + op . name ,
AuthRequired : true ,
2020-06-05 22:26:46 +08:00
NeedsRequest : op . needsRequest ,
2019-06-17 16:34:30 +08:00
Fn : func ( ctx context . Context , in rc . Params ) ( rc . Params , error ) {
return rcSingleCommand ( ctx , in , op . name , op . noRemote )
2018-10-26 21:51:28 +08:00
} ,
Title : op . title ,
Help : ` This takes the following parameters
2020-10-14 05:49:58 +08:00
- fs - a remote name string e . g . "drive:"
2018-10-26 21:51:28 +08:00
` + remote + op.help + `
See the [ ` + op.name + ` command ] ( / commands / rclone_ ` + op.name + ` / ) command for more information on the above .
` ,
} )
}
}
2020-10-14 05:49:58 +08:00
// Run a single command, e.g. Mkdir
2019-06-17 16:34:30 +08:00
func rcSingleCommand ( ctx context . Context , in rc . Params , name string , noRemote bool ) ( out rc . Params , err error ) {
2018-10-26 21:51:28 +08:00
var (
f fs . Fs
remote string
)
if noRemote {
2020-11-05 23:18:51 +08:00
f , err = rc . GetFs ( ctx , in )
2018-10-26 21:51:28 +08:00
} else {
2020-11-05 23:18:51 +08:00
f , remote , err = rc . GetFsAndRemote ( ctx , in )
2018-10-26 21:51:28 +08:00
}
if err != nil {
return nil , err
}
switch name {
case "mkdir" :
2019-06-17 16:34:30 +08:00
return nil , Mkdir ( ctx , f , remote )
2018-10-26 21:51:28 +08:00
case "rmdir" :
2019-06-17 16:34:30 +08:00
return nil , Rmdir ( ctx , f , remote )
2018-10-26 21:51:28 +08:00
case "purge" :
2019-06-17 16:34:30 +08:00
return nil , Purge ( ctx , f , remote )
2018-10-26 21:51:28 +08:00
case "rmdirs" :
leaveRoot , err := in . GetBool ( "leaveRoot" )
if rc . NotErrParamNotFound ( err ) {
return nil , err
}
2019-06-17 16:34:30 +08:00
return nil , Rmdirs ( ctx , f , remote , leaveRoot )
2018-10-26 21:51:28 +08:00
case "delete" :
2019-06-17 16:34:30 +08:00
return nil , Delete ( ctx , f )
2018-10-26 21:51:28 +08:00
case "deletefile" :
2019-06-17 16:34:30 +08:00
o , err := f . NewObject ( ctx , remote )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , err
}
2019-06-17 16:34:30 +08:00
return nil , DeleteFile ( ctx , o )
2018-10-26 21:51:28 +08:00
case "copyurl" :
url , err := in . GetString ( "url" )
if err != nil {
return nil , err
}
2019-09-04 00:25:19 +08:00
autoFilename , _ := in . GetBool ( "autoFilename" )
2020-04-19 19:40:17 +08:00
noClobber , _ := in . GetBool ( "noClobber" )
2019-09-04 00:25:19 +08:00
2020-04-19 19:40:17 +08:00
_ , err = CopyURL ( ctx , f , remote , url , autoFilename , noClobber )
2018-10-26 21:51:28 +08:00
return nil , err
2020-06-12 14:04:30 +08:00
case "uploadfile" :
var request * http . Request
request , err := in . GetHTTPRequest ( )
if err != nil {
return nil , err
}
contentType := request . Header . Get ( "Content-Type" )
mediaType , params , err := mime . ParseMediaType ( contentType )
if err != nil {
return nil , err
}
if strings . HasPrefix ( mediaType , "multipart/" ) {
mr := multipart . NewReader ( request . Body , params [ "boundary" ] )
for {
p , err := mr . NextPart ( )
if err == io . EOF {
return nil , nil
}
if err != nil {
return nil , err
}
if p . FileName ( ) != "" {
2020-08-11 00:09:46 +08:00
obj , err := Rcat ( ctx , f , path . Join ( remote , p . FileName ( ) ) , p , time . Now ( ) )
2020-06-12 14:04:30 +08:00
if err != nil {
return nil , err
}
fs . Debugf ( obj , "Upload Succeeded" )
}
}
}
return nil , nil
2018-10-26 21:51:28 +08:00
case "cleanup" :
2019-06-17 16:34:30 +08:00
return nil , CleanUp ( ctx , f )
2018-10-26 21:51:28 +08:00
}
panic ( "unknown rcSingleCommand type" )
}
func init ( ) {
rc . Add ( rc . Call {
Path : "operations/size" ,
AuthRequired : true ,
Fn : rcSize ,
Title : "Count the number of bytes and files in remote" ,
Help : ` This takes the following parameters
2020-10-14 05:49:58 +08:00
- fs - a remote name string e . g . "drive:path/to/dir"
2018-10-26 21:51:28 +08:00
Returns
- count - number of files
- bytes - number of bytes in those files
See the [ size command ] ( / commands / rclone_size / ) command for more information on the above .
` ,
} )
}
2019-03-12 00:59:27 +08:00
// Size a directory
2019-06-17 16:34:30 +08:00
func rcSize ( ctx context . Context , in rc . Params ) ( out rc . Params , err error ) {
2020-11-05 23:18:51 +08:00
f , err := rc . GetFs ( ctx , in )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , err
}
2019-06-17 16:34:30 +08:00
count , bytes , err := Count ( ctx , f )
2018-10-26 21:51:28 +08:00
if err != nil {
return nil , err
}
out = make ( rc . Params )
out [ "count" ] = count
out [ "bytes" ] = bytes
return out , nil
}
2019-03-12 00:59:27 +08:00
func init ( ) {
rc . Add ( rc . Call {
Path : "operations/publiclink" ,
AuthRequired : true ,
Fn : rcPublicLink ,
Title : "Create or retrieve a public link to the given file or folder." ,
Help : ` This takes the following parameters
2020-10-14 05:49:58 +08:00
- fs - a remote name string e . g . "drive:"
- remote - a path within that remote e . g . "dir"
2020-06-01 05:18:01 +08:00
- unlink - boolean - if set removes the link rather than adding it ( optional )
2020-10-14 05:49:58 +08:00
- expire - string - the expiry time of the link e . g . "1d" ( optional )
2019-03-12 00:59:27 +08:00
Returns
- url - URL of the resource
See the [ link command ] ( / commands / rclone_link / ) command for more information on the above .
` ,
} )
}
// Make a public link
2019-06-17 16:34:30 +08:00
func rcPublicLink ( ctx context . Context , in rc . Params ) ( out rc . Params , err error ) {
2020-11-05 23:18:51 +08:00
f , remote , err := rc . GetFsAndRemote ( ctx , in )
2019-03-12 00:59:27 +08:00
if err != nil {
return nil , err
}
2020-06-01 05:18:01 +08:00
unlink , _ := in . GetBool ( "unlink" )
expire , err := in . GetDuration ( "expire" )
if err != nil && ! rc . IsErrParamNotFound ( err ) {
return nil , err
}
url , err := PublicLink ( ctx , f , remote , fs . Duration ( expire ) , unlink )
2019-03-12 00:59:27 +08:00
if err != nil {
return nil , err
}
out = make ( rc . Params )
out [ "url" ] = url
return out , nil
}
2019-06-08 16:19:07 +08:00
func init ( ) {
rc . Add ( rc . Call {
Path : "operations/fsinfo" ,
Fn : rcFsInfo ,
Title : "Return information about the remote" ,
Help : ` This takes the following parameters
2020-10-14 05:49:58 +08:00
- fs - a remote name string e . g . "drive:"
2019-06-08 16:19:07 +08:00
This returns info about the remote passed in ;
` + " ` ` ` " + `
{
// optional features and whether they are available or not
"Features" : {
"About" : true ,
"BucketBased" : false ,
"CanHaveEmptyDirectories" : true ,
"CaseInsensitive" : false ,
"ChangeNotify" : false ,
"CleanUp" : false ,
"Copy" : false ,
"DirCacheFlush" : false ,
"DirMove" : true ,
"DuplicateFiles" : false ,
"GetTier" : false ,
"ListR" : false ,
"MergeDirs" : false ,
"Move" : true ,
"OpenWriterAt" : true ,
"PublicLink" : false ,
"Purge" : true ,
"PutStream" : true ,
"PutUnchecked" : false ,
"ReadMimeType" : false ,
"ServerSideAcrossConfigs" : false ,
"SetTier" : false ,
"SetWrapper" : false ,
"UnWrap" : false ,
"WrapFs" : false ,
"WriteMimeType" : false
} ,
// Names of hashes available
"Hashes" : [
"MD5" ,
"SHA-1" ,
"DropboxHash" ,
"QuickXorHash"
] ,
"Name" : "local" , // Name as created
"Precision" : 1 , // Precision of timestamps in ns
"Root" : "/" , // Path as created
"String" : "Local file system at /" // how the remote will appear in logs
}
` + " ` ` ` " + `
This command does not have a command line equivalent so use this instead :
rclone rc -- loopback operations / fsinfo fs = remote :
` ,
} )
}
// Fsinfo the remote
2019-06-17 16:34:30 +08:00
func rcFsInfo ( ctx context . Context , in rc . Params ) ( out rc . Params , err error ) {
2020-11-05 23:18:51 +08:00
f , err := rc . GetFs ( ctx , in )
2019-06-08 16:19:07 +08:00
if err != nil {
return nil , err
}
info := GetFsInfo ( f )
err = rc . Reshape ( & out , info )
if err != nil {
return nil , errors . Wrap ( err , "fsinfo Reshape failed" )
}
return out , nil
}
2020-04-28 20:02:19 +08:00
func init ( ) {
rc . Add ( rc . Call {
Path : "backend/command" ,
AuthRequired : true ,
Fn : rcBackend ,
Title : "Runs a backend command." ,
Help : ` This takes the following parameters
- command - a string with the command name
2020-10-14 05:49:58 +08:00
- fs - a remote name string e . g . "drive:"
2020-04-28 20:02:19 +08:00
- arg - a list of arguments for the backend command
- opt - a map of string to string of options
Returns
- result - result from the backend command
For example
rclone rc backend / command command = noop fs = . - o echo = yes - o blue - a path1 - a path2
Returns
` + " ` ` ` " + `
{
"result" : {
"arg" : [
"path1" ,
"path2"
] ,
"name" : "noop" ,
"opt" : {
"blue" : "" ,
"echo" : "yes"
}
}
}
` + " ` ` ` " + `
Note that this is the direct equivalent of using this "backend"
command :
rclone backend noop . - o echo = yes - o blue path1 path2
2020-05-20 18:39:20 +08:00
Note that arguments must be preceded by the "-a" flag
2020-04-28 20:02:19 +08:00
See the [ backend ] ( / commands / rclone_backend / ) command for more information .
` ,
} )
}
// Make a public link
func rcBackend ( ctx context . Context , in rc . Params ) ( out rc . Params , err error ) {
2020-11-05 23:18:51 +08:00
f , err := rc . GetFs ( ctx , in )
2020-04-28 20:02:19 +08:00
if err != nil {
return nil , err
}
doCommand := f . Features ( ) . Command
if doCommand == nil {
return nil , errors . Errorf ( "%v: doesn't support backend commands" , f )
}
command , err := in . GetString ( "command" )
if err != nil {
return nil , err
}
var opt = map [ string ] string { }
err = in . GetStructMissingOK ( "opt" , & opt )
if err != nil {
return nil , err
}
var arg = [ ] string { }
err = in . GetStructMissingOK ( "arg" , & arg )
if err != nil {
return nil , err
}
result , err := doCommand ( context . Background ( ) , command , arg , opt )
if err != nil {
return nil , errors . Wrapf ( err , "command %q failed" , command )
}
out = make ( rc . Params )
out [ "result" ] = result
return out , nil
}