2022-08-28 13:21:57 +02:00
// Package opendrive provides an interface to the OpenDrive storage system.
2017-06-02 08:51:38 +02:00
package opendrive
import (
2019-06-17 10:34:30 +02:00
"context"
2021-11-04 11:12:57 +01:00
"errors"
2018-04-26 23:02:31 +02:00
"fmt"
2017-06-02 08:51:38 +02:00
"io"
"net/http"
2018-10-16 22:55:19 +02:00
"net/url"
2017-07-17 07:36:45 +02:00
"path"
2017-06-02 08:51:38 +02:00
"strconv"
"strings"
"time"
2019-07-28 19:47:38 +02:00
"github.com/rclone/rclone/fs"
2020-01-14 18:33:35 +01:00
"github.com/rclone/rclone/fs/config"
2019-07-28 19:47:38 +02:00
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache"
2020-01-14 18:33:35 +01:00
"github.com/rclone/rclone/lib/encoder"
2019-07-28 19:47:38 +02:00
"github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers"
"github.com/rclone/rclone/lib/rest"
2017-06-02 08:51:38 +02:00
)
const (
defaultEndpoint = "https://dev.opendrive.com/api/v1"
minSleep = 10 * time . Millisecond
maxSleep = 5 * time . Minute
decayConstant = 1 // bigger for slower decay, exponential
)
// Register with Fs
func init ( ) {
fs . Register ( & fs . RegInfo {
Name : "opendrive" ,
2017-07-17 07:36:45 +02:00
Description : "OpenDrive" ,
2017-06-02 08:51:38 +02:00
NewFs : NewFs ,
Options : [ ] fs . Option { {
2023-07-06 18:55:53 +02:00
Name : "username" ,
Help : "Username." ,
Required : true ,
Sensitive : true ,
2017-06-02 08:51:38 +02:00
} , {
Name : "password" ,
Help : "Password." ,
IsPassword : true ,
2018-05-14 19:06:57 +02:00
Required : true ,
2020-01-14 18:33:35 +01:00
} , {
Name : config . ConfigEncoding ,
Help : config . ConfigEncodingHelp ,
Advanced : true ,
2020-01-14 22:51:49 +01:00
// List of replaced characters:
// < (less than) -> '< ' // FULLWIDTH LESS-THAN SIGN
// > (greater than) -> '> ' // FULLWIDTH GREATER-THAN SIGN
// : (colon) -> ': ' // FULLWIDTH COLON
// " (double quote) -> '" ' // FULLWIDTH QUOTATION MARK
// \ (backslash) -> '\ ' // FULLWIDTH REVERSE SOLIDUS
// | (vertical line) -> '| ' // FULLWIDTH VERTICAL LINE
// ? (question mark) -> '? ' // FULLWIDTH QUESTION MARK
// * (asterisk) -> '* ' // FULLWIDTH ASTERISK
//
2020-05-25 08:05:53 +02:00
// Additionally names can't begin or end with an ASCII whitespace.
2020-01-14 22:51:49 +01:00
// List of replaced characters:
// (space) -> '␠' // SYMBOL FOR SPACE
// (horizontal tab) -> '␉' // SYMBOL FOR HORIZONTAL TABULATION
// (line feed) -> '␊' // SYMBOL FOR LINE FEED
// (vertical tab) -> '␋' // SYMBOL FOR VERTICAL TABULATION
// (carriage return) -> '␍' // SYMBOL FOR CARRIAGE RETURN
//
// Also encode invalid UTF-8 bytes as json doesn't handle them properly.
//
// https://www.opendrive.com/wp-content/uploads/guides/OpenDrive_API_guide.pdf
Default : ( encoder . Base |
encoder . EncodeWin |
encoder . EncodeLeftCrLfHtVt |
encoder . EncodeRightCrLfHtVt |
encoder . EncodeBackSlash |
encoder . EncodeLeftSpace |
encoder . EncodeRightSpace |
encoder . EncodeInvalidUtf8 ) ,
2019-11-15 12:22:13 +01:00
} , {
Name : "chunk_size" ,
Help : ` Files will be uploaded in chunks this size .
Note that these chunks are buffered in memory so increasing them will
increase memory use . ` ,
2021-03-02 20:11:57 +01:00
Default : 10 * fs . Mebi ,
2019-11-15 12:22:13 +01:00
Advanced : true ,
2025-02-11 13:43:10 +01:00
} , {
Name : "access" ,
Help : "Files and folders will be uploaded with this access permission (default private)" ,
Default : "private" ,
Advanced : true ,
Examples : [ ] fs . OptionExample { {
Value : "private" ,
Help : "The file or folder access can be granted in a way that will allow select users to view, read or write what is absolutely essential for them." ,
} , {
Value : "public" ,
Help : "The file or folder can be downloaded by anyone from a web browser. The link can be shared in any way," ,
} , {
Value : "hidden" ,
Help : "The file or folder can be accessed has the same restrictions as Public if the user knows the URL of the file or folder link in order to access the contents" ,
} } ,
2017-06-02 08:51:38 +02:00
} } ,
} )
}
2018-05-14 19:06:57 +02:00
// Options defines the configuration for this backend
type Options struct {
2019-11-15 12:22:13 +01:00
UserName string ` config:"username" `
Password string ` config:"password" `
Enc encoder . MultiEncoder ` config:"encoding" `
ChunkSize fs . SizeSuffix ` config:"chunk_size" `
2025-02-11 13:43:10 +01:00
Access string ` config:"access" `
2018-05-14 19:06:57 +02:00
}
2018-04-26 23:02:31 +02:00
// Fs represents a remote server
2017-06-02 08:51:38 +02:00
type Fs struct {
name string // name of this remote
2017-07-17 07:36:45 +02:00
root string // the path we are working on
2018-05-14 19:06:57 +02:00
opt Options // parsed options
2017-06-02 08:51:38 +02:00
features * fs . Features // optional features
2018-04-26 23:02:31 +02:00
srv * rest . Client // the connection to the server
2019-02-09 21:52:15 +01:00
pacer * fs . Pacer // To pace and retry the API calls
2017-06-02 08:51:38 +02:00
session UserSessionInfo // contains the session data
dirCache * dircache . DirCache // Map of directory path to directory id
}
2018-04-26 23:02:31 +02:00
// Object describes an object
2017-06-02 08:51:38 +02:00
type Object struct {
fs * Fs // what this object is part of
remote string // The remote path
2018-04-26 23:02:31 +02:00
id string // ID of the file
2021-03-11 18:40:29 +01:00
parent string // ID of the parent directory
2017-06-02 08:51:38 +02:00
modTime time . Time // The modified time of the object if known
md5 string // MD5 hash if known
size int64 // Size of the object
}
2018-04-26 23:02:31 +02:00
// parsePath parses an incoming 'url'
2017-06-02 08:51:38 +02:00
func parsePath ( path string ) ( root string ) {
root = strings . Trim ( path , "/" )
return
}
// ------------------------------------------------------------
// Name of the remote (as passed into NewFs)
func ( f * Fs ) Name ( ) string {
return f . name
}
// Root of the remote (as passed into NewFs)
func ( f * Fs ) Root ( ) string {
2017-07-17 07:36:45 +02:00
return f . root
2017-06-02 08:51:38 +02:00
}
// String converts this Fs to a string
func ( f * Fs ) String ( ) string {
2017-07-17 07:36:45 +02:00
return fmt . Sprintf ( "OpenDrive root '%s'" , f . root )
2017-06-02 08:51:38 +02:00
}
// Features returns the optional features of this Fs
func ( f * Fs ) Features ( ) * fs . Features {
return f . features
}
// Hashes returns the supported hash sets.
2017-07-17 07:36:45 +02:00
func ( f * Fs ) Hashes ( ) hash . Set {
return hash . Set ( hash . MD5 )
2017-06-02 08:51:38 +02:00
}
2018-04-26 23:02:31 +02:00
// DirCacheFlush resets the directory cache - used in testing as an
// optional interface
func ( f * Fs ) DirCacheFlush ( ) {
f . dirCache . ResetRoot ( )
}
2019-02-07 18:41:17 +01:00
// NewFs constructs an Fs from the path, bucket:path
2020-11-05 16:18:51 +01:00
func NewFs ( ctx context . Context , name , root string , m configmap . Mapper ) ( fs . Fs , error ) {
2018-05-14 19:06:57 +02:00
// Parse config into Options struct
opt := new ( Options )
err := configstruct . Set ( m , opt )
if err != nil {
return nil , err
}
2017-06-02 08:51:38 +02:00
root = parsePath ( root )
2018-05-14 19:06:57 +02:00
if opt . UserName == "" {
2017-06-02 08:51:38 +02:00
return nil , errors . New ( "username not found" )
}
2018-05-14 19:06:57 +02:00
opt . Password , err = obscure . Reveal ( opt . Password )
2017-06-02 08:51:38 +02:00
if err != nil {
2018-05-14 19:06:57 +02:00
return nil , errors . New ( "password could not revealed" )
2017-06-02 08:51:38 +02:00
}
2018-05-14 19:06:57 +02:00
if opt . Password == "" {
2017-06-02 08:51:38 +02:00
return nil , errors . New ( "password not found" )
}
f := & Fs {
2018-05-14 19:06:57 +02:00
name : name ,
root : root ,
opt : * opt ,
2020-11-13 16:24:43 +01:00
srv : rest . NewClient ( fshttp . NewClient ( ctx ) ) . SetErrorHandler ( errorHandler ) ,
2020-11-05 12:33:32 +01:00
pacer : fs . NewPacer ( ctx , pacer . NewDefault ( pacer . MinSleep ( minSleep ) , pacer . MaxSleep ( maxSleep ) , pacer . DecayConstant ( decayConstant ) ) ) ,
2017-06-02 08:51:38 +02:00
}
f . dirCache = dircache . New ( root , "0" , f )
// set the rootURL for the REST client
f . srv . SetRoot ( defaultEndpoint )
// get sessionID
var resp * http . Response
err = f . pacer . Call ( func ( ) ( bool , error ) {
2018-05-14 19:06:57 +02:00
account := Account { Username : opt . UserName , Password : opt . Password }
2017-06-02 08:51:38 +02:00
opts := rest . Opts {
Method : "POST" ,
Path : "/session/login.json" ,
}
2019-09-04 21:00:37 +02:00
resp , err = f . srv . CallJSON ( ctx , & opts , & account , & f . session )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return nil , fmt . Errorf ( "failed to create session: %w" , err )
2017-06-02 08:51:38 +02:00
}
2017-07-17 07:36:45 +02:00
fs . Debugf ( nil , "Starting OpenDrive session with ID: %s" , f . session . SessionID )
2017-06-02 08:51:38 +02:00
2018-04-26 23:02:31 +02:00
f . features = ( & fs . Features {
CaseInsensitive : true ,
CanHaveEmptyDirectories : true ,
2020-11-05 17:00:40 +01:00
} ) . Fill ( ctx , f )
2017-06-02 08:51:38 +02:00
// Find the current root
2019-06-17 10:34:30 +02:00
err = f . dirCache . FindRoot ( ctx , false )
2017-06-02 08:51:38 +02:00
if err != nil {
// Assume it is a file
newRoot , remote := dircache . SplitPath ( root )
2018-10-14 15:41:26 +02:00
tempF := * f
tempF . dirCache = dircache . New ( newRoot , "0" , & tempF )
tempF . root = newRoot
2017-06-02 08:51:38 +02:00
// Make new Fs which is the parent
2019-06-17 10:34:30 +02:00
err = tempF . dirCache . FindRoot ( ctx , false )
2017-06-02 08:51:38 +02:00
if err != nil {
// No root so return old f
return f , nil
}
2021-03-11 18:40:29 +01:00
_ , err := tempF . newObjectWithInfo ( ctx , remote , nil , "" )
2017-06-02 08:51:38 +02:00
if err != nil {
if err == fs . ErrorObjectNotFound {
// File doesn't exist so return old f
return f , nil
}
return nil , err
}
2018-10-14 15:41:26 +02:00
// XXX: update the old f here instead of returning tempF, since
// `features` were already filled with functions having *f as a receiver.
2019-07-28 19:47:38 +02:00
// See https://github.com/rclone/rclone/issues/2182
2018-10-14 15:41:26 +02:00
f . dirCache = tempF . dirCache
f . root = tempF . root
2017-06-02 08:51:38 +02:00
// return an error with an fs which points to the parent
2018-10-14 15:41:26 +02:00
return f , fs . ErrorIsFile
2017-06-02 08:51:38 +02:00
}
return f , nil
}
2017-07-17 07:36:45 +02:00
// rootSlash returns root with a slash on if it is empty, otherwise empty string
func ( f * Fs ) rootSlash ( ) string {
if f . root == "" {
return f . root
}
return f . root + "/"
}
2017-06-02 08:51:38 +02:00
// errorHandler parses a non 2xx error response into an error
func errorHandler ( resp * http . Response ) error {
2018-04-26 23:02:31 +02:00
errResponse := new ( Error )
err := rest . DecodeJSON ( resp , & errResponse )
if err != nil {
fs . Debugf ( nil , "Couldn't decode error response: %v" , err )
}
if errResponse . Info . Code == 0 {
errResponse . Info . Code = resp . StatusCode
}
if errResponse . Info . Message == "" {
errResponse . Info . Message = "Unknown " + resp . Status
}
return errResponse
2017-06-02 08:51:38 +02:00
}
2017-07-17 07:36:45 +02:00
// Mkdir creates the folder if it doesn't exist
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Mkdir ( ctx context . Context , dir string ) error {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Mkdir(\"%s\")", dir)
2020-05-11 18:24:37 +02:00
_ , err := f . dirCache . FindDir ( ctx , dir , true )
2017-06-02 08:51:38 +02:00
return err
}
2017-07-17 07:36:45 +02:00
// deleteObject removes an object by ID
2019-09-04 21:00:37 +02:00
func ( f * Fs ) deleteObject ( ctx context . Context , id string ) error {
2017-07-17 07:36:45 +02:00
return f . pacer . Call ( func ( ) ( bool , error ) {
removeDirData := removeFolder { SessionID : f . session . SessionID , FolderID : id }
opts := rest . Opts {
Method : "POST" ,
NoResponse : true ,
Path : "/folder/remove.json" ,
}
2019-09-04 21:00:37 +02:00
resp , err := f . srv . CallJSON ( ctx , & opts , & removeDirData , nil )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
}
// purgeCheck remotes the root directory, if check is set then it
// refuses to do so if it has anything in
2019-06-17 10:34:30 +02:00
func ( f * Fs ) purgeCheck ( ctx context . Context , dir string , check bool ) error {
2017-07-17 07:36:45 +02:00
root := path . Join ( f . root , dir )
if root == "" {
return errors . New ( "can't purge root directory" )
}
dc := f . dirCache
2019-06-17 10:34:30 +02:00
rootID , err := dc . FindDir ( ctx , dir , false )
2017-07-17 07:36:45 +02:00
if err != nil {
return err
}
2019-09-04 21:00:37 +02:00
item , err := f . readMetaDataForFolderID ( ctx , rootID )
2017-07-17 07:36:45 +02:00
if err != nil {
return err
}
if check && len ( item . Files ) != 0 {
return errors . New ( "folder not empty" )
}
2019-09-04 21:00:37 +02:00
err = f . deleteObject ( ctx , rootID )
2017-07-17 07:36:45 +02:00
if err != nil {
return err
}
f . dirCache . FlushDir ( dir )
return nil
}
// Rmdir deletes the root folder
2017-06-02 08:51:38 +02:00
//
// Returns an error if it isn't empty
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Rmdir ( ctx context . Context , dir string ) error {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Rmdir(\"%s\")", path.Join(f.root, dir))
2019-06-17 10:34:30 +02:00
return f . purgeCheck ( ctx , dir , true )
2017-06-02 08:51:38 +02:00
}
// Precision of the remote
func ( f * Fs ) Precision ( ) time . Duration {
2017-07-17 07:36:45 +02:00
return time . Second
}
2020-10-13 23:43:40 +02:00
// Copy src to this remote using server-side copy operations.
2017-07-17 07:36:45 +02:00
//
2022-08-05 17:35:41 +02:00
// This is stored with the remote path given.
2017-07-17 07:36:45 +02:00
//
2022-08-05 17:35:41 +02:00
// It returns the destination Object and a possible error.
2017-07-17 07:36:45 +02:00
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantCopy
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Copy ( ctx context . Context , src fs . Object , remote string ) ( fs . Object , error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Copy(%v)", remote)
2017-07-17 07:36:45 +02:00
srcObj , ok := src . ( * Object )
if ! ok {
fs . Debugf ( src , "Can't copy - not same remote type" )
return nil , fs . ErrorCantCopy
}
2019-06-17 10:34:30 +02:00
err := srcObj . readMetaData ( ctx )
2017-07-17 07:36:45 +02:00
if err != nil {
return nil , err
}
srcPath := srcObj . fs . rootSlash ( ) + srcObj . remote
dstPath := f . rootSlash ( ) + remote
2022-06-08 22:25:17 +02:00
if strings . EqualFold ( srcPath , dstPath ) {
2022-01-24 22:53:55 +01:00
return nil , fmt . Errorf ( "can't copy %q -> %q as are same name when lowercase" , srcPath , dstPath )
2017-07-17 07:36:45 +02:00
}
// Create temporary object
2019-06-17 10:34:30 +02:00
dstObj , leaf , directoryID , err := f . createObject ( ctx , remote , srcObj . modTime , srcObj . size )
2017-07-17 07:36:45 +02:00
if err != nil {
return nil , err
}
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "...%#v\n...%#v", remote, directoryID)
2017-07-17 07:36:45 +02:00
// Copy the object
var resp * http . Response
2018-04-26 23:02:31 +02:00
response := moveCopyFileResponse { }
2017-07-17 07:36:45 +02:00
err = f . pacer . Call ( func ( ) ( bool , error ) {
2018-04-26 23:02:31 +02:00
copyFileData := moveCopyFile {
2017-07-17 07:36:45 +02:00
SessionID : f . session . SessionID ,
SrcFileID : srcObj . id ,
DstFolderID : directoryID ,
Move : "false" ,
OverwriteIfExists : "true" ,
2018-04-26 23:02:31 +02:00
NewFileName : leaf ,
2017-07-17 07:36:45 +02:00
}
opts := rest . Opts {
Method : "POST" ,
Path : "/file/move_copy.json" ,
}
2019-09-04 21:00:37 +02:00
resp , err = f . srv . CallJSON ( ctx , & opts , & copyFileData , & response )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
if err != nil {
return nil , err
}
size , _ := strconv . ParseInt ( response . Size , 10 , 64 )
dstObj . id = response . FileID
dstObj . size = size
return dstObj , nil
}
2024-09-17 18:20:42 +02:00
// About gets quota information
func ( f * Fs ) About ( ctx context . Context ) ( usage * fs . Usage , err error ) {
var uInfo usersInfoResponse
var resp * http . Response
err = f . pacer . Call ( func ( ) ( bool , error ) {
opts := rest . Opts {
Method : "GET" ,
Path : "/users/info.json/" + f . session . SessionID ,
}
resp , err = f . srv . CallJSON ( ctx , & opts , nil , & uInfo )
return f . shouldRetry ( ctx , resp , err )
} )
if err != nil {
return nil , err
}
usage = & fs . Usage {
Used : fs . NewUsageValue ( uInfo . StorageUsed ) ,
Total : fs . NewUsageValue ( uInfo . MaxStorage * 1024 * 1024 ) , // MaxStorage appears to be in MB
Free : fs . NewUsageValue ( uInfo . MaxStorage * 1024 * 1024 - uInfo . StorageUsed ) ,
}
return usage , nil
}
2020-10-13 23:43:40 +02:00
// Move src to this remote using server-side move operations.
2017-07-17 07:36:45 +02:00
//
2022-08-05 17:35:41 +02:00
// This is stored with the remote path given.
2017-07-17 07:36:45 +02:00
//
2022-08-05 17:35:41 +02:00
// It returns the destination Object and a possible error.
2017-07-17 07:36:45 +02:00
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantMove
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Move ( ctx context . Context , src fs . Object , remote string ) ( fs . Object , error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Move(%v)", remote)
2017-07-17 07:36:45 +02:00
srcObj , ok := src . ( * Object )
if ! ok {
fs . Debugf ( src , "Can't move - not same remote type" )
return nil , fs . ErrorCantCopy
}
2019-06-17 10:34:30 +02:00
err := srcObj . readMetaData ( ctx )
2017-07-17 07:36:45 +02:00
if err != nil {
return nil , err
}
// Create temporary object
2019-06-17 10:34:30 +02:00
dstObj , leaf , directoryID , err := f . createObject ( ctx , remote , srcObj . modTime , srcObj . size )
2017-07-17 07:36:45 +02:00
if err != nil {
return nil , err
}
2022-01-24 22:53:55 +01:00
// move_copy will silently truncate new filenames
if len ( leaf ) > 255 {
fs . Debugf ( src , "Can't move file: name (%q) exceeds 255 char" , leaf )
return nil , fs . ErrorFileNameTooLong
}
2024-02-18 11:03:39 +01:00
moveCopyFileData := moveCopyFile {
SessionID : f . session . SessionID ,
SrcFileID : srcObj . id ,
DstFolderID : directoryID ,
Move : "true" ,
OverwriteIfExists : "true" ,
NewFileName : leaf ,
}
opts := rest . Opts {
Method : "POST" ,
Path : "/file/move_copy.json" ,
}
var request interface { } = moveCopyFileData
// use /file/rename.json if moving within the same directory
_ , srcDirID , err := srcObj . fs . dirCache . FindPath ( ctx , srcObj . remote , false )
if err != nil {
return nil , err
}
if srcDirID == directoryID {
fs . Debugf ( src , "same parent dir (%v) - using file/rename instead of move_copy for %s" , directoryID , remote )
renameFileData := renameFile {
SessionID : f . session . SessionID ,
FileID : srcObj . id ,
NewFileName : leaf ,
}
opts . Path = "/file/rename.json"
request = renameFileData
}
// Move the object
2017-07-17 07:36:45 +02:00
var resp * http . Response
2018-04-26 23:02:31 +02:00
response := moveCopyFileResponse { }
2017-07-17 07:36:45 +02:00
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-02-18 11:03:39 +01:00
resp , err = f . srv . CallJSON ( ctx , & opts , & request , & response )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
if err != nil {
return nil , err
}
size , _ := strconv . ParseInt ( response . Size , 10 , 64 )
dstObj . id = response . FileID
dstObj . size = size
return dstObj , nil
}
// DirMove moves src, srcRemote to this remote at dstRemote
2020-10-13 23:43:40 +02:00
// using server-side move operations.
2017-07-17 07:36:45 +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
2019-06-17 10:34:30 +02:00
func ( f * Fs ) DirMove ( ctx context . Context , src fs . Fs , srcRemote , dstRemote string ) ( err error ) {
2017-07-17 07:36:45 +02:00
srcFs , ok := src . ( * Fs )
if ! ok {
2018-04-26 23:02:31 +02:00
fs . Debugf ( srcFs , "Can't move directory - not same remote type" )
2017-07-17 07:36:45 +02:00
return fs . ErrorCantDirMove
}
2024-02-18 11:03:39 +01:00
srcID , srcDirectoryID , _ , dstDirectoryID , dstLeaf , err := f . dirCache . DirMove ( ctx , srcFs . dirCache , srcFs . root , srcRemote , f . root , dstRemote )
2018-04-26 23:02:31 +02:00
if err != nil {
return err
}
2024-02-18 11:03:39 +01:00
// move_copy will silently truncate new filenames
if len ( dstLeaf ) > 255 {
fs . Debugf ( src , "Can't move folder: name (%q) exceeds 255 char" , dstLeaf )
return fs . ErrorFileNameTooLong
}
moveFolderData := moveCopyFolder {
SessionID : f . session . SessionID ,
FolderID : srcID ,
DstFolderID : dstDirectoryID ,
Move : "true" ,
NewFolderName : dstLeaf ,
}
opts := rest . Opts {
Method : "POST" ,
Path : "/folder/move_copy.json" ,
}
var request interface { } = moveFolderData
// use /folder/rename.json if moving within the same parent directory
if srcDirectoryID == dstDirectoryID {
fs . Debugf ( dstRemote , "same parent dir (%v) - using folder/rename instead of move_copy" , srcDirectoryID )
renameFolderData := renameFolder {
SessionID : f . session . SessionID ,
FolderID : srcID ,
FolderName : dstLeaf ,
}
opts . Path = "/folder/rename.json"
request = renameFolderData
}
2018-04-26 23:02:31 +02:00
// Do the move
2017-07-17 07:36:45 +02:00
var resp * http . Response
2018-04-26 23:02:31 +02:00
response := moveCopyFolderResponse { }
2017-07-17 07:36:45 +02:00
err = f . pacer . Call ( func ( ) ( bool , error ) {
2024-02-18 11:03:39 +01:00
resp , err = f . srv . CallJSON ( ctx , & opts , & request , & response )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
if err != nil {
fs . Debugf ( src , "DirMove error %v" , err )
return err
}
srcFs . dirCache . FlushDir ( srcRemote )
return nil
}
2020-06-04 23:25:14 +02:00
// Purge deletes all the files in the directory
2017-07-17 07:36:45 +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()
2020-06-04 23:25:14 +02:00
func ( f * Fs ) Purge ( ctx context . Context , dir string ) error {
return f . purgeCheck ( ctx , dir , false )
2017-06-02 08:51:38 +02:00
}
// Return an Object from a path
//
// If it can't be found it returns the error fs.ErrorObjectNotFound.
2021-03-11 18:40:29 +01:00
func ( f * Fs ) newObjectWithInfo ( ctx context . Context , remote string , file * File , parent string ) ( fs . Object , error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "newObjectWithInfo(%s, %v)", remote, file)
2017-06-02 08:51:38 +02:00
var o * Object
if nil != file {
o = & Object {
fs : f ,
remote : remote ,
id : file . FileID ,
2021-03-11 18:40:29 +01:00
parent : parent ,
2017-06-02 08:51:38 +02:00
modTime : time . Unix ( file . DateModified , 0 ) ,
size : file . Size ,
2017-07-17 07:36:45 +02:00
md5 : file . FileHash ,
2017-06-02 08:51:38 +02:00
}
} else {
o = & Object {
fs : f ,
remote : remote ,
}
2019-06-17 10:34:30 +02:00
err := o . readMetaData ( ctx )
2017-06-02 08:51:38 +02:00
if err != nil {
return nil , err
}
}
return o , nil
}
// NewObject finds the Object at remote. If it can't be found
// it returns the error fs.ErrorObjectNotFound.
2019-06-17 10:34:30 +02:00
func ( f * Fs ) NewObject ( ctx context . Context , remote string ) ( fs . Object , error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "NewObject(\"%s\")", remote)
2021-03-11 18:40:29 +01:00
return f . newObjectWithInfo ( ctx , remote , nil , "" )
2017-06-02 08:51:38 +02:00
}
// Creates from the parameters passed in a half finished Object which
// must have setMetaData called on it
//
2022-08-05 17:35:41 +02:00
// Returns the object, leaf, directoryID and error.
2017-06-02 08:51:38 +02:00
//
// Used to create new objects
2019-06-17 10:34:30 +02:00
func ( f * Fs ) createObject ( ctx context . Context , remote string , modTime time . Time , size int64 ) ( o * Object , leaf string , directoryID string , err error ) {
2017-06-02 08:51:38 +02:00
// Create the directory for the object if it doesn't exist
2020-05-11 18:24:37 +02:00
leaf , directoryID , err = f . dirCache . FindPath ( ctx , remote , true )
2017-06-02 08:51:38 +02:00
if err != nil {
return nil , leaf , directoryID , err
}
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "\n...leaf %#v\n...id %#v", leaf, directoryID)
2017-06-02 08:51:38 +02:00
// Temporary Object under construction
o = & Object {
fs : f ,
remote : remote ,
}
2020-01-14 18:33:35 +01:00
return o , f . opt . Enc . FromStandardName ( leaf ) , directoryID , nil
2017-06-02 08:51:38 +02:00
}
2017-07-17 07:36:45 +02:00
// readMetaDataForPath reads the metadata from the path
2019-09-04 21:00:37 +02:00
func ( f * Fs ) readMetaDataForFolderID ( ctx context . Context , id string ) ( info * FolderList , err error ) {
2017-07-17 07:36:45 +02:00
var resp * http . Response
opts := rest . Opts {
Method : "GET" ,
Path : "/folder/list.json/" + f . session . SessionID + "/" + id ,
}
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-04 21:00:37 +02:00
resp , err = f . srv . CallJSON ( ctx , & opts , nil , & info )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
if err != nil {
return nil , err
}
return info , err
}
2017-06-02 08:51:38 +02:00
// Put the object into the bucket
//
2022-08-05 17:35:41 +02:00
// Copy the reader in to the new object which is returned.
2017-06-02 08:51:38 +02:00
//
// The new object may have been created if an error is returned
2019-06-17 10:34:30 +02:00
func ( f * Fs ) Put ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) ( fs . Object , error ) {
2017-06-02 08:51:38 +02:00
remote := src . Remote ( )
size := src . Size ( )
2019-06-17 10:34:30 +02:00
modTime := src . ModTime ( ctx )
2017-06-02 08:51:38 +02:00
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Put(%s)", remote)
2017-06-02 08:51:38 +02:00
2019-06-17 10:34:30 +02:00
o , leaf , directoryID , err := f . createObject ( ctx , remote , modTime , size )
2017-06-02 08:51:38 +02:00
if err != nil {
return nil , err
}
2017-07-17 07:36:45 +02:00
2022-06-08 22:25:17 +02:00
if o . id == "" {
2018-04-26 23:02:31 +02:00
// Attempt to read ID, ignore error
// FIXME is this correct?
2019-06-17 10:34:30 +02:00
_ = o . readMetaData ( ctx )
2017-07-17 07:36:45 +02:00
}
2022-06-08 22:25:17 +02:00
if o . id == "" {
2020-05-20 12:39:20 +02:00
// We need to create an ID for this file
2017-07-17 07:36:45 +02:00
var resp * http . Response
response := createFileResponse { }
err := o . fs . pacer . Call ( func ( ) ( bool , error ) {
2018-11-02 13:14:30 +01:00
createFileData := createFile {
SessionID : o . fs . session . SessionID ,
FolderID : directoryID ,
Name : leaf ,
}
2017-07-17 07:36:45 +02:00
opts := rest . Opts {
2020-03-21 22:50:17 +01:00
Method : "POST" ,
Options : options ,
Path : "/upload/create_file.json" ,
2017-07-17 07:36:45 +02:00
}
2019-09-04 21:00:37 +02:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , & createFileData , & response )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return nil , fmt . Errorf ( "failed to create file: %w" , err )
2017-07-17 07:36:45 +02:00
}
o . id = response . FileID
}
2019-06-17 10:34:30 +02:00
return o , o . Update ( ctx , in , src , options ... )
2017-06-02 08:51:38 +02:00
}
// retryErrorCodes is a slice of error codes that we will retry
var retryErrorCodes = [ ] int {
401 , // Unauthorized (seen in "Token has expired")
408 , // Request Timeout
2018-04-26 23:02:31 +02:00
423 , // Locked - get this on folders sometimes
2017-06-02 08:51:38 +02:00
429 , // Rate exceeded.
500 , // Get occasional 500 Internal Server Error
502 , // Bad Gateway when doing big listings
503 , // Service Unavailable
504 , // Gateway Time-out
}
// shouldRetry returns a boolean as to whether this resp and err
// deserve to be retried. It returns the err as a convenience
2021-03-11 15:44:01 +01:00
func ( f * Fs ) shouldRetry ( ctx context . Context , resp * http . Response , err error ) ( bool , error ) {
if fserrors . ContextError ( ctx , & err ) {
return false , err
}
2017-07-17 07:36:45 +02:00
return fserrors . ShouldRetry ( err ) || fserrors . ShouldRetryHTTP ( resp , retryErrorCodes ) , err
2017-06-02 08:51:38 +02:00
}
2025-02-11 13:43:10 +01:00
// getAccessLevel is a helper function to determine access level integer
func getAccessLevel ( access string ) int64 {
var accessLevel int64
switch access {
case "private" :
accessLevel = 0
case "public" :
accessLevel = 1
case "hidden" :
accessLevel = 2
default :
accessLevel = 0
fs . Errorf ( nil , "Invalid access: %s, defaulting to private" , access )
}
return accessLevel
}
2017-07-17 07:36:45 +02:00
// DirCacher methods
2017-06-02 08:51:38 +02:00
// CreateDir makes a directory with pathID as parent and name leaf
2019-06-17 10:34:30 +02:00
func ( f * Fs ) CreateDir ( ctx context . Context , pathID , leaf string ) ( newID string , err error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(f, "CreateDir(%q, %q)\n", pathID, replaceReservedChars(leaf))
2017-07-17 07:36:45 +02:00
var resp * http . Response
response := createFolderResponse { }
err = f . pacer . Call ( func ( ) ( bool , error ) {
createDirData := createFolder {
SessionID : f . session . SessionID ,
2020-01-14 18:33:35 +01:00
FolderName : f . opt . Enc . FromStandardName ( leaf ) ,
2017-07-17 07:36:45 +02:00
FolderSubParent : pathID ,
2025-02-11 13:43:10 +01:00
FolderIsPublic : getAccessLevel ( f . opt . Access ) ,
2017-07-17 07:36:45 +02:00
FolderPublicUpl : 0 ,
FolderPublicDisplay : 0 ,
FolderPublicDnl : 0 ,
}
opts := rest . Opts {
Method : "POST" ,
Path : "/folder.json" ,
}
2019-09-04 21:00:37 +02:00
resp , err = f . srv . CallJSON ( ctx , & opts , & createDirData , & response )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
if err != nil {
return "" , err
}
return response . FolderID , nil
2017-06-02 08:51:38 +02:00
}
// FindLeaf finds a directory of name leaf in the folder with ID pathID
2019-06-17 10:34:30 +02:00
func ( f * Fs ) FindLeaf ( ctx context . Context , pathID , leaf string ) ( pathIDOut string , found bool , err error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "FindLeaf(\"%s\", \"%s\")", pathID, leaf)
2017-06-02 08:51:38 +02:00
if pathID == "0" && leaf == "" {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Found OpenDrive root")
2017-06-02 08:51:38 +02:00
// that's the root directory
return pathID , true , nil
}
// get the folderIDs
var resp * http . Response
folderList := FolderList { }
err = f . pacer . Call ( func ( ) ( bool , error ) {
opts := rest . Opts {
Method : "GET" ,
Path : "/folder/list.json/" + f . session . SessionID + "/" + pathID ,
}
2019-09-04 21:00:37 +02:00
resp , err = f . srv . CallJSON ( ctx , & opts , nil , & folderList )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return "" , false , fmt . Errorf ( "failed to get folder list: %w" , err )
2017-06-02 08:51:38 +02:00
}
2020-01-14 18:33:35 +01:00
leaf = f . opt . Enc . FromStandardName ( leaf )
2017-06-02 08:51:38 +02:00
for _ , folder := range folderList . Folders {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
2017-06-02 08:51:38 +02:00
2021-01-27 14:44:19 +01:00
if strings . EqualFold ( leaf , folder . Name ) {
2017-06-02 08:51:38 +02:00
// found
return folder . FolderID , true , nil
}
}
return "" , false , nil
}
2017-07-17 07:36:45 +02:00
// List the objects and directories in dir into entries. The
// entries can be returned in any order but should be for a
// complete directory.
//
// dir should be "" to list the root, and should not have
// trailing slashes.
//
// This should return ErrDirNotFound if the directory isn't
// found.
2019-06-17 10:34:30 +02:00
func ( f * Fs ) List ( ctx context . Context , dir string ) ( entries fs . DirEntries , err error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "List(%v)", dir)
2019-06-17 10:34:30 +02:00
directoryID , err := f . dirCache . FindDir ( ctx , dir , false )
2017-07-17 07:36:45 +02:00
if err != nil {
return nil , err
}
2017-06-02 08:51:38 +02:00
var resp * http . Response
2017-07-17 07:36:45 +02:00
opts := rest . Opts {
Method : "GET" ,
Path : "/folder/list.json/" + f . session . SessionID + "/" + directoryID ,
}
2017-06-02 08:51:38 +02:00
folderList := FolderList { }
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-04 21:00:37 +02:00
resp , err = f . srv . CallJSON ( ctx , & opts , nil , & folderList )
2021-03-11 15:44:01 +01:00
return f . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2023-09-05 18:59:03 +02:00
if apiError , ok := err . ( * Error ) ; ok {
// Work around a bug maybe in opendrive or maybe in rclone.
//
// We should know whether the folder exists or not by the call to
// FindDir above so exactly why it is not found here is a mystery.
//
// This manifests as a failure in fs/sync TestSyncOverlapWithFilter
if apiError . Info . Message == "Folder is already deleted" {
return fs . DirEntries { } , nil
}
}
2021-11-04 11:12:57 +01:00
return nil , fmt . Errorf ( "failed to get folder list: %w" , err )
2017-06-02 08:51:38 +02:00
}
for _ , folder := range folderList . Folders {
2020-01-14 18:33:35 +01:00
folder . Name = f . opt . Enc . ToStandardName ( folder . Name )
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
2017-07-17 07:36:45 +02:00
remote := path . Join ( dir , folder . Name )
// cache the directory ID for later lookups
f . dirCache . Put ( remote , folder . FolderID )
2019-01-11 18:17:46 +01:00
d := fs . NewDir ( remote , time . Unix ( folder . DateModified , 0 ) ) . SetID ( folder . FolderID )
2017-07-17 07:36:45 +02:00
d . SetItems ( int64 ( folder . ChildFolders ) )
2021-03-11 18:40:29 +01:00
d . SetParentID ( directoryID )
2017-07-17 07:36:45 +02:00
entries = append ( entries , d )
2017-06-02 08:51:38 +02:00
}
for _ , file := range folderList . Files {
2020-01-14 18:33:35 +01:00
file . Name = f . opt . Enc . ToStandardName ( file . Name )
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID)
2017-07-17 07:36:45 +02:00
remote := path . Join ( dir , file . Name )
2021-03-11 18:40:29 +01:00
o , err := f . newObjectWithInfo ( ctx , remote , & file , directoryID )
2017-06-02 08:51:38 +02:00
if err != nil {
2017-07-17 07:36:45 +02:00
return nil , err
2017-06-02 08:51:38 +02:00
}
2017-07-17 07:36:45 +02:00
entries = append ( entries , o )
2017-06-02 08:51:38 +02:00
}
2017-07-17 07:36:45 +02:00
return entries , nil
2017-06-02 08:51:38 +02:00
}
// ------------------------------------------------------------
// Fs returns the parent Fs
func ( o * Object ) Fs ( ) fs . Info {
return o . fs
}
// Return a string version
func ( o * Object ) String ( ) string {
if o == nil {
return "<nil>"
}
return o . remote
}
// Remote returns the remote path
func ( o * Object ) Remote ( ) string {
return o . remote
}
// Hash returns the Md5sum of an object returning a lowercase hex string
2019-06-17 10:34:30 +02:00
func ( o * Object ) Hash ( ctx context . Context , t hash . Type ) ( string , error ) {
2017-07-17 07:36:45 +02:00
if t != hash . MD5 {
return "" , hash . ErrUnsupported
2017-06-02 08:51:38 +02:00
}
return o . md5 , nil
}
// Size returns the size of an object in bytes
func ( o * Object ) Size ( ) int64 {
return o . size // Object is likely PENDING
}
// 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
2019-06-17 10:34:30 +02:00
func ( o * Object ) ModTime ( ctx context . Context ) time . Time {
2017-06-02 08:51:38 +02:00
return o . modTime
}
// SetModTime sets the modification time of the local fs object
2019-06-17 10:34:30 +02:00
func ( o * Object ) SetModTime ( ctx context . Context , modTime time . Time ) error {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "SetModTime(%v)", modTime.String())
2017-07-17 07:36:45 +02:00
opts := rest . Opts {
Method : "PUT" ,
NoResponse : true ,
Path : "/file/filesettings.json" ,
}
2018-11-02 13:14:30 +01:00
update := modTimeFile {
SessionID : o . fs . session . SessionID ,
FileID : o . id ,
FileModificationTime : strconv . FormatInt ( modTime . Unix ( ) , 10 ) ,
}
2017-07-17 07:36:45 +02:00
err := o . fs . pacer . Call ( func ( ) ( bool , error ) {
2019-09-04 21:00:37 +02:00
resp , err := o . fs . srv . CallJSON ( ctx , & opts , & update , nil )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
o . modTime = modTime
return err
2017-06-02 08:51:38 +02:00
}
// Open an object for read
2019-06-17 10:34:30 +02:00
func ( o * Object ) Open ( ctx context . Context , options ... fs . OpenOption ) ( in io . ReadCloser , err error ) {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Open(\"%v\")", o.remote)
fs . FixRangeOption ( options , o . size )
opts := rest . Opts {
Method : "GET" ,
Path : "/download/file.json/" + o . id + "?session_id=" + o . fs . session . SessionID ,
Options : options ,
2017-07-17 07:36:45 +02:00
}
2017-06-02 08:51:38 +02:00
var resp * http . Response
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2019-09-04 21:00:37 +02:00
resp , err = o . fs . srv . Call ( ctx , & opts )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return nil , fmt . Errorf ( "failed to open file): %w" , err )
2017-06-02 08:51:38 +02:00
}
return resp . Body , nil
}
// Remove an object
2019-06-17 10:34:30 +02:00
func ( o * Object ) Remove ( ctx context . Context ) error {
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Remove(\"%s\")", o.id)
2017-07-17 07:36:45 +02:00
return o . fs . pacer . Call ( func ( ) ( bool , error ) {
opts := rest . Opts {
Method : "DELETE" ,
NoResponse : true ,
Path : "/file.json/" + o . fs . session . SessionID + "/" + o . id ,
}
2019-09-04 21:00:37 +02:00
resp , err := o . fs . srv . Call ( ctx , & opts )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
2017-06-02 08:51:38 +02:00
}
// Storable returns a boolean showing whether this object storable
func ( o * Object ) Storable ( ) bool {
return true
}
// Update the object with the contents of the io.Reader, modTime and size
//
// The new object may have been created if an error is returned
2019-06-17 10:34:30 +02:00
func ( o * Object ) Update ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) error {
2017-06-02 08:51:38 +02:00
size := src . Size ( )
2019-06-17 10:34:30 +02:00
modTime := src . ModTime ( ctx )
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Update(\"%s\", \"%s\")", o.id, o.remote)
2017-06-02 08:51:38 +02:00
// Open file for upload
var resp * http . Response
openResponse := openUploadResponse { }
2017-07-17 07:36:45 +02:00
err := o . fs . pacer . Call ( func ( ) ( bool , error ) {
2017-06-02 08:51:38 +02:00
openUploadData := openUpload { SessionID : o . fs . session . SessionID , FileID : o . id , Size : size }
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "PreOpen: %#v", openUploadData)
2017-06-02 08:51:38 +02:00
opts := rest . Opts {
2020-03-21 22:50:17 +01:00
Method : "POST" ,
Options : options ,
Path : "/upload/open_file_upload.json" ,
2017-06-02 08:51:38 +02:00
}
2019-09-04 21:00:37 +02:00
resp , err := o . fs . srv . CallJSON ( ctx , & opts , & openUploadData , & openResponse )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return fmt . Errorf ( "failed to create file: %w" , err )
2017-06-02 08:51:38 +02:00
}
2017-07-17 07:36:45 +02:00
// resp.Body.Close()
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "PostOpen: %#v", openResponse)
2017-06-02 08:51:38 +02:00
2019-11-15 12:22:13 +01:00
buf := make ( [ ] byte , o . fs . opt . ChunkSize )
2017-06-02 08:51:38 +02:00
chunkOffset := int64 ( 0 )
remainingBytes := size
chunkCounter := 0
for remainingBytes > 0 {
2019-11-15 12:22:13 +01:00
currentChunkSize := int64 ( o . fs . opt . ChunkSize )
2017-06-02 08:51:38 +02:00
if currentChunkSize > remainingBytes {
currentChunkSize = remainingBytes
}
remainingBytes -= currentChunkSize
2018-04-26 23:02:31 +02:00
fs . Debugf ( o , "Uploading chunk %d, size=%d, remain=%d" , chunkCounter , currentChunkSize , remainingBytes )
2017-06-02 08:51:38 +02:00
2018-10-13 14:09:38 +02:00
chunk := readers . NewRepeatableLimitReaderBuffer ( in , buf , currentChunkSize )
2019-07-23 15:20:51 +02:00
var reply uploadFileChunkReply
2017-06-02 08:51:38 +02:00
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2018-10-13 14:09:38 +02:00
// seek to the start in case this is a retry
if _ , err = chunk . Seek ( 0 , io . SeekStart ) ; err != nil {
return false , err
}
2017-06-02 08:51:38 +02:00
opts := rest . Opts {
2019-07-23 15:20:51 +02:00
Method : "POST" ,
Path : "/upload/upload_file_chunk.json" ,
Body : chunk ,
MultipartParams : url . Values {
"session_id" : [ ] string { o . fs . session . SessionID } ,
"file_id" : [ ] string { o . id } ,
"temp_location" : [ ] string { openResponse . TempLocation } ,
"chunk_offset" : [ ] string { strconv . FormatInt ( chunkOffset , 10 ) } ,
"chunk_size" : [ ] string { strconv . FormatInt ( currentChunkSize , 10 ) } ,
} ,
MultipartContentName : "file_data" , // ..name of the parameter which is the attached file
MultipartFileName : o . remote , // ..name of the file for the attached file
2017-06-02 08:51:38 +02:00
}
2019-09-04 21:00:37 +02:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , nil , & reply )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return fmt . Errorf ( "failed to create file: %w" , err )
2017-06-02 08:51:38 +02:00
}
2019-07-23 15:20:51 +02:00
if reply . TotalWritten != currentChunkSize {
2021-11-04 11:12:57 +01:00
return fmt . Errorf ( "failed to create file: incomplete write of %d/%d bytes" , reply . TotalWritten , currentChunkSize )
2018-04-26 23:02:31 +02:00
}
2017-06-02 08:51:38 +02:00
chunkCounter ++
chunkOffset += currentChunkSize
}
2017-07-17 07:36:45 +02:00
// Close file for upload
2017-06-02 08:51:38 +02:00
closeResponse := closeUploadResponse { }
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
closeUploadData := closeUpload { SessionID : o . fs . session . SessionID , FileID : o . id , Size : size , TempLocation : openResponse . TempLocation }
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "PreClose: %#v", closeUploadData)
2017-06-02 08:51:38 +02:00
opts := rest . Opts {
Method : "POST" ,
Path : "/upload/close_file_upload.json" ,
}
2019-09-04 21:00:37 +02:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , & closeUploadData , & closeResponse )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return fmt . Errorf ( "failed to create file: %w" , err )
2017-06-02 08:51:38 +02:00
}
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "PostClose: %#v", closeResponse)
2017-07-17 07:36:45 +02:00
o . id = closeResponse . FileID
o . size = closeResponse . Size
2018-06-15 15:50:17 +02:00
// Set the mod time now
2019-06-17 10:34:30 +02:00
err = o . SetModTime ( ctx , modTime )
2017-07-17 07:36:45 +02:00
if err != nil {
return err
}
// Set permissions
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2025-02-11 13:43:10 +01:00
update := permissions { SessionID : o . fs . session . SessionID , FileID : o . id , FileIsPublic : getAccessLevel ( o . fs . opt . Access ) }
2018-04-26 23:02:31 +02:00
// fs.Debugf(nil, "Permissions : %#v", update)
2017-07-17 07:36:45 +02:00
opts := rest . Opts {
Method : "POST" ,
NoResponse : true ,
Path : "/file/access.json" ,
}
2019-09-04 21:00:37 +02:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , & update , nil )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-07-17 07:36:45 +02:00
} )
if err != nil {
return err
}
2017-06-02 08:51:38 +02:00
2019-06-17 10:34:30 +02:00
return o . readMetaData ( ctx )
2017-06-02 08:51:38 +02:00
}
2019-06-17 10:34:30 +02:00
func ( o * Object ) readMetaData ( ctx context . Context ) ( err error ) {
2020-05-11 18:24:37 +02:00
leaf , directoryID , err := o . fs . dirCache . FindPath ( ctx , o . remote , false )
2017-06-02 08:51:38 +02:00
if err != nil {
if err == fs . ErrorDirNotFound {
return fs . ErrorObjectNotFound
}
return err
}
var resp * http . Response
2022-01-24 22:53:55 +01:00
fileInfo := File { }
// If we know the object id perform a direct lookup
// because the /folder/itembyname.json endpoint is unreliable:
// newly created objects take an arbitrary amount of time to show up
if o . id != "" {
2017-06-02 08:51:38 +02:00
opts := rest . Opts {
Method : "GET" ,
2022-01-24 22:53:55 +01:00
Path : fmt . Sprintf ( "/file/info.json/%s?session_id=%s" ,
o . id , o . fs . session . SessionID ) ,
}
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
resp , err = o . fs . srv . CallJSON ( ctx , & opts , nil , & fileInfo )
return o . fs . shouldRetry ( ctx , resp , err )
} )
if err != nil {
return fmt . Errorf ( "failed to get fileinfo: %w" , err )
2017-06-02 08:51:38 +02:00
}
2022-01-24 22:53:55 +01:00
o . id = fileInfo . FileID
o . modTime = time . Unix ( fileInfo . DateModified , 0 )
o . md5 = fileInfo . FileHash
o . size = fileInfo . Size
return nil
}
folderList := FolderList { }
opts := rest . Opts {
Method : "GET" ,
Path : fmt . Sprintf ( "/folder/itembyname.json/%s/%s?name=%s" ,
o . fs . session . SessionID , directoryID , url . QueryEscape ( o . fs . opt . Enc . FromStandardName ( leaf ) ) ) ,
}
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2019-09-04 21:00:37 +02:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , nil , & folderList )
2021-03-11 15:44:01 +01:00
return o . fs . shouldRetry ( ctx , resp , err )
2017-06-02 08:51:38 +02:00
} )
if err != nil {
2021-11-04 11:12:57 +01:00
return fmt . Errorf ( "failed to get folder list: %w" , err )
2017-06-02 08:51:38 +02:00
}
if len ( folderList . Files ) == 0 {
return fs . ErrorObjectNotFound
}
2022-01-24 22:53:55 +01:00
fileInfo = folderList . Files [ 0 ]
o . id = fileInfo . FileID
o . modTime = time . Unix ( fileInfo . DateModified , 0 )
o . md5 = fileInfo . FileHash
o . size = fileInfo . Size
2017-06-02 08:51:38 +02:00
return nil
}
2018-04-26 23:02:31 +02:00
2018-05-13 10:16:56 +02:00
// ID returns the ID of the Object if known, or "" if not
func ( o * Object ) ID ( ) string {
return o . id
}
2021-03-11 18:40:29 +01:00
// ParentID returns the ID of the Object parent directory if known, or "" if not
func ( o * Object ) ParentID ( ) string {
return o . parent
}
2018-04-26 23:02:31 +02:00
// Check the interfaces are satisfied
var (
_ fs . Fs = ( * Fs ) ( nil )
_ fs . Purger = ( * Fs ) ( nil )
_ fs . Copier = ( * Fs ) ( nil )
_ fs . Mover = ( * Fs ) ( nil )
_ fs . DirMover = ( * Fs ) ( nil )
_ fs . DirCacheFlusher = ( * Fs ) ( nil )
2024-09-17 18:20:42 +02:00
_ fs . Abouter = ( * Fs ) ( nil )
2018-04-26 23:02:31 +02:00
_ fs . Object = ( * Object ) ( nil )
2018-05-13 10:16:56 +02:00
_ fs . IDer = ( * Object ) ( nil )
2021-03-11 18:40:29 +01:00
_ fs . ParentIDer = ( * Object ) ( nil )
2018-04-26 23:02:31 +02:00
)