2022-08-28 19:21:57 +08:00
// Package yandex provides an interface to the Yandex storage system.
2015-12-07 12:01:03 +08:00
package yandex
import (
2019-06-17 16:34:30 +08:00
"context"
2015-12-07 12:01:03 +08:00
"encoding/json"
2021-11-04 18:12:57 +08:00
"errors"
2015-12-07 12:01:03 +08:00
"fmt"
"io"
2018-11-20 01:27:00 +08:00
"net/http"
"net/url"
2015-12-07 12:01:03 +08:00
"path"
2018-11-20 01:27:00 +08:00
"strconv"
2015-12-07 12:01:03 +08:00
"strings"
"time"
2019-07-29 01:47:38 +08:00
"github.com/rclone/rclone/backend/yandex/api"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"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/hash"
2024-11-08 22:01:51 +08:00
"github.com/rclone/rclone/fs/operations"
2020-01-15 01:33:35 +08:00
"github.com/rclone/rclone/lib/encoder"
2019-07-29 01:47:38 +08:00
"github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer"
2024-08-30 01:25:08 +08:00
"github.com/rclone/rclone/lib/random"
2019-07-29 01:47:38 +08:00
"github.com/rclone/rclone/lib/readers"
"github.com/rclone/rclone/lib/rest"
2015-12-07 12:01:03 +08:00
"golang.org/x/oauth2"
)
2022-08-05 23:35:41 +08:00
// oAuth
2015-12-07 12:01:03 +08:00
const (
2016-02-29 03:57:19 +08:00
rcloneClientID = "ac39b43b9eba4cae8ffb788c06d816a8"
2016-08-14 19:04:43 +08:00
rcloneEncryptedClientSecret = "EfyyNZ3YUEwXM5yAhi72G9YwKn2mkFrYwJNS7cY0TJAhFlX9K-uJFbGlpO-RYjrJ"
2018-11-20 01:27:00 +08:00
rootURL = "https://cloud-api.yandex.com/v1/disk"
minSleep = 10 * time . Millisecond
maxSleep = 2 * time . Second // may needs to be increased, testing needed
decayConstant = 2 // bigger for slower decay, exponential
2024-08-30 01:25:08 +08:00
userAgentTemplae = ` Yandex.Disk { "os":"windows","dtype":"ydisk3","vsn":"3.2.37.4977","id":"6BD01244C7A94456BBCEE7EEC990AEAD","id2":"0F370CD40C594A4783BC839C846B999C","session_id":"%s"} `
2015-12-07 12:01:03 +08:00
)
// Globals
var (
// Description of how to auth for this app
oauthConfig = & oauth2 . Config {
Endpoint : oauth2 . Endpoint {
AuthURL : "https://oauth.yandex.com/authorize" , //same as https://oauth.yandex.ru/authorize
TokenURL : "https://oauth.yandex.com/token" , //same as https://oauth.yandex.ru/token
} ,
ClientID : rcloneClientID ,
2018-01-19 04:19:55 +08:00
ClientSecret : obscure . MustReveal ( rcloneEncryptedClientSecret ) ,
2015-12-07 12:01:03 +08:00
RedirectURL : oauthutil . RedirectURL ,
}
)
// Register with Fs
func init ( ) {
2016-02-18 19:35:25 +08:00
fs . Register ( & fs . RegInfo {
2016-02-16 02:11:53 +08:00
Name : "yandex" ,
Description : "Yandex Disk" ,
NewFs : NewFs ,
2021-04-29 16:28:18 +08:00
Config : func ( ctx context . Context , name string , m configmap . Mapper , config fs . ConfigIn ) ( * fs . ConfigOut , error ) {
return oauthutil . ConfigOut ( "" , & oauthutil . Options {
OAuth2Config : oauthConfig ,
} )
2015-12-07 12:01:03 +08:00
} ,
2020-08-02 07:32:21 +08:00
Options : append ( oauthutil . SharedOptions , [ ] fs . Option { {
2021-11-15 15:58:38 +08:00
Name : "hard_delete" ,
Help : "Delete files permanently rather than putting them into the trash." ,
Default : false ,
Advanced : true ,
} , {
2020-01-15 01:33:35 +08:00
Name : config . ConfigEncoding ,
Help : config . ConfigEncodingHelp ,
Advanced : true ,
2020-01-15 05:51:49 +08:00
// Of the control characters \t \n \r are allowed
// it doesn't seem worth making an exception for this
Default : ( encoder . Display |
encoder . EncodeInvalidUtf8 ) ,
2024-08-30 01:25:08 +08:00
} , {
Name : "spoof_ua" ,
Help : "Set the user agent to match an official version of the yandex disk client. May help with upload performance." ,
Default : true ,
Advanced : true ,
Hide : fs . OptionHideConfigurator ,
2020-08-02 07:32:21 +08:00
} } ... ) ,
2015-12-07 12:01:03 +08:00
} )
}
2018-05-15 01:06:57 +08:00
// Options defines the configuration for this backend
type Options struct {
2024-08-30 01:25:08 +08:00
Token string ` config:"token" `
HardDelete bool ` config:"hard_delete" `
Enc encoder . MultiEncoder ` config:"encoding" `
SpoofUserAgent bool ` config:"spoof_ua" `
2018-05-15 01:06:57 +08:00
}
2015-12-07 12:01:03 +08:00
// Fs represents a remote yandex
type Fs struct {
2018-05-05 18:33:01 +08:00
name string
2020-11-05 19:33:32 +08:00
root string // root path
opt Options // parsed options
ci * fs . ConfigInfo // global config
features * fs . Features // optional features
srv * rest . Client // the connection to the yandex server
pacer * fs . Pacer // pacer for API calls
diskRoot string // root path with "disk:/" container name
2015-12-07 12:01:03 +08:00
}
// Object describes a swift object
type Object struct {
2018-11-20 01:27:00 +08:00
fs * Fs // what this object is part of
remote string // The remote path
hasMetaData bool // whether info below has been set
md5sum string // The MD5Sum of the object
size int64 // Bytes in the object
modTime time . Time // Modified time of the object
mimeType string // Content type according to the server
2015-12-07 12:01:03 +08:00
}
// ------------------------------------------------------------
// 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 {
return f . root
}
// String converts this Fs to a string
func ( f * Fs ) String ( ) string {
return fmt . Sprintf ( "Yandex %s" , f . root )
}
2018-11-20 01:27:00 +08:00
// Precision return the precision of this Fs
func ( f * Fs ) Precision ( ) time . Duration {
return time . Nanosecond
}
// Hashes returns the supported hash sets.
func ( f * Fs ) Hashes ( ) hash . Set {
return hash . Set ( hash . MD5 )
}
2017-01-14 01:21:47 +08:00
// Features returns the optional features of this Fs
func ( f * Fs ) Features ( ) * fs . Features {
return f . features
}
2018-11-20 01:27:00 +08:00
// retryErrorCodes is a slice of error codes that we will retry
var retryErrorCodes = [ ] int {
429 , // Too Many Requests.
500 , // Internal Server Error
502 , // Bad Gateway
503 , // Service Unavailable
504 , // Gateway Timeout
509 , // Bandwidth Limit Exceeded
}
// 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 22:44:01 +08:00
func shouldRetry ( ctx context . Context , resp * http . Response , err error ) ( bool , error ) {
if fserrors . ContextError ( ctx , & err ) {
return false , err
}
2018-11-20 01:27:00 +08:00
return fserrors . ShouldRetry ( err ) || fserrors . ShouldRetryHTTP ( resp , retryErrorCodes ) , err
}
// errorHandler parses a non 2xx error response into an error
func errorHandler ( resp * http . Response ) error {
// Decode error response
errResponse := new ( api . ErrorResponse )
err := rest . DecodeJSON ( resp , & errResponse )
if err != nil {
fs . Debugf ( nil , "Couldn't decode error response: %v" , err )
}
if errResponse . Message == "" {
errResponse . Message = resp . Status
}
if errResponse . StatusCode == 0 {
errResponse . StatusCode = resp . StatusCode
}
return errResponse
}
// Sets root in f
func ( f * Fs ) setRoot ( root string ) {
//Set root path
f . root = strings . Trim ( root , "/" )
//Set disk root path.
//Adding "disk:" to root path as all paths on disk start with it
var diskRoot string
if f . root == "" {
diskRoot = "disk:/"
} else {
diskRoot = "disk:/" + f . root + "/"
}
f . diskRoot = diskRoot
}
2020-05-20 18:39:20 +08:00
// filePath returns an escaped file path (f.root, file)
2018-11-20 01:27:00 +08:00
func ( f * Fs ) filePath ( file string ) string {
return path . Join ( f . diskRoot , file )
}
2020-05-20 18:39:20 +08:00
// dirPath returns an escaped file path (f.root, file) ending with '/'
2018-11-20 01:27:00 +08:00
func ( f * Fs ) dirPath ( file string ) string {
return path . Join ( f . diskRoot , file ) + "/"
}
2019-09-05 03:00:37 +08:00
func ( f * Fs ) readMetaDataForPath ( ctx context . Context , path string , options * api . ResourceInfoRequestOptions ) ( * api . ResourceInfoResponse , error ) {
2018-11-20 01:27:00 +08:00
opts := rest . Opts {
Method : "GET" ,
Path : "/resources" ,
Parameters : url . Values { } ,
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "path" , f . opt . Enc . FromStandardPath ( path ) )
2018-11-20 01:27:00 +08:00
if options . SortMode != nil {
opts . Parameters . Set ( "sort" , options . SortMode . String ( ) )
}
if options . Limit != 0 {
opts . Parameters . Set ( "limit" , strconv . FormatUint ( options . Limit , 10 ) )
}
if options . Offset != 0 {
opts . Parameters . Set ( "offset" , strconv . FormatUint ( options . Offset , 10 ) )
}
if options . Fields != nil {
opts . Parameters . Set ( "fields" , strings . Join ( options . Fields , "," ) )
}
var err error
var info api . ResourceInfoResponse
var resp * http . Response
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . CallJSON ( ctx , & opts , nil , & info )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
2015-12-07 12:01:03 +08:00
if err != nil {
return nil , err
}
2018-11-20 01:27:00 +08:00
2020-01-15 01:33:35 +08:00
info . Name = f . opt . Enc . ToStandardName ( info . Name )
2018-11-20 01:27:00 +08:00
return & info , nil
2015-12-07 12:01:03 +08:00
}
// NewFs constructs an Fs from the path, container:path
2020-11-05 23:18:51 +08:00
func NewFs ( ctx context . Context , name , root string , m configmap . Mapper ) ( fs . Fs , error ) {
2018-05-15 01:06:57 +08:00
// Parse config into Options struct
opt := new ( Options )
err := configstruct . Set ( m , opt )
if err != nil {
return nil , err
}
2024-08-30 01:25:08 +08:00
ctx , ci := fs . AddConfig ( ctx )
if fs . ConfigOptionsInfo . Get ( "user_agent" ) . IsDefault ( ) && opt . SpoofUserAgent {
randomSessionID , _ := random . Password ( 128 )
ci . UserAgent = fmt . Sprintf ( userAgentTemplae , randomSessionID )
}
2018-11-20 01:27:00 +08:00
token , err := oauthutil . GetToken ( name , m )
2015-12-07 12:01:03 +08:00
if err != nil {
2021-11-04 18:12:57 +08:00
return nil , fmt . Errorf ( "couldn't read OAuth token: %w" , err )
2018-11-20 01:27:00 +08:00
}
if token . RefreshToken == "" {
2021-04-07 04:27:34 +08:00
return nil , errors . New ( "unable to get RefreshToken. If you are upgrading from older versions of rclone, please run `rclone config` and re-configure this backend" )
2018-11-20 01:27:00 +08:00
}
if token . TokenType != "OAuth" {
token . TokenType = "OAuth"
err = oauthutil . PutToken ( name , m , token , false )
if err != nil {
2021-11-04 18:12:57 +08:00
return nil , fmt . Errorf ( "couldn't save OAuth token: %w" , err )
2018-11-20 01:27:00 +08:00
}
2024-08-18 22:58:35 +08:00
fs . Logf ( nil , "Automatically upgraded OAuth config." )
2018-11-20 01:27:00 +08:00
}
2020-11-06 02:02:26 +08:00
oAuthClient , _ , err := oauthutil . NewClient ( ctx , name , m , oauthConfig )
2018-11-20 01:27:00 +08:00
if err != nil {
2021-11-04 18:12:57 +08:00
return nil , fmt . Errorf ( "failed to configure Yandex: %w" , err )
2015-12-07 12:01:03 +08:00
}
f := & Fs {
2018-11-20 01:27:00 +08:00
name : name ,
opt : * opt ,
2020-11-05 19:33:32 +08:00
ci : ci ,
2018-11-20 01:27:00 +08:00
srv : rest . NewClient ( oAuthClient ) . SetRoot ( rootURL ) ,
2020-11-05 19:33:32 +08:00
pacer : fs . NewPacer ( ctx , pacer . NewDefault ( pacer . MinSleep ( minSleep ) , pacer . MaxSleep ( maxSleep ) , pacer . DecayConstant ( decayConstant ) ) ) ,
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
f . setRoot ( root )
2017-08-09 22:27:43 +08:00
f . features = ( & fs . Features {
ReadMimeType : true ,
2020-11-30 00:37:36 +08:00
WriteMimeType : false , // Yandex ignores the mime type we send
2017-08-09 22:27:43 +08:00
CanHaveEmptyDirectories : true ,
2020-11-06 00:00:40 +08:00
} ) . Fill ( ctx , f )
2018-11-20 01:27:00 +08:00
f . srv . SetErrorHandler ( errorHandler )
2015-12-07 12:01:03 +08:00
// Check to see if the object exists and is a file
//request object meta info
2018-11-20 01:27:00 +08:00
// Check to see if the object exists and is a file
//request object meta info
2019-09-05 03:00:37 +08:00
if info , err := f . readMetaDataForPath ( ctx , f . diskRoot , & api . ResourceInfoRequestOptions { } ) ; err != nil {
2018-11-20 01:27:00 +08:00
2024-05-31 21:18:56 +08:00
} else if info . ResourceType == "file" {
rootDir := path . Dir ( root )
if rootDir == "." {
rootDir = ""
2015-12-07 12:01:03 +08:00
}
2024-05-31 21:18:56 +08:00
f . setRoot ( rootDir )
// return an error with an fs which points to the parent
return f , fs . ErrorIsFile
2015-12-07 12:01:03 +08:00
}
return f , nil
}
2017-06-30 17:54:14 +08:00
// Convert a list item into a DirEntry
2019-09-05 03:00:37 +08:00
func ( f * Fs ) itemToDirEntry ( ctx context . Context , remote string , object * api . ResourceInfoResponse ) ( fs . DirEntry , error ) {
2017-06-12 05:43:31 +08:00
switch object . ResourceType {
case "dir" :
t , err := time . Parse ( time . RFC3339Nano , object . Modified )
if err != nil {
2021-11-04 18:12:57 +08:00
return nil , fmt . Errorf ( "error parsing time in directory item: %w" , err )
2017-06-12 05:43:31 +08:00
}
2019-01-12 01:17:46 +08:00
d := fs . NewDir ( remote , t ) . SetSize ( object . Size )
2017-06-12 05:43:31 +08:00
return d , nil
case "file" :
2019-09-05 03:00:37 +08:00
o , err := f . newObjectWithInfo ( ctx , remote , object )
2017-06-12 05:43:31 +08:00
if err != nil {
return nil , err
}
return o , nil
default :
fs . Debugf ( f , "Unknown resource type %q" , object . ResourceType )
}
return nil , nil
}
2016-04-22 03:06:21 +08:00
2017-06-12 05:43:31 +08: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 16:34:30 +08:00
func ( f * Fs ) List ( ctx context . Context , dir string ) ( entries fs . DirEntries , err error ) {
2018-11-20 01:27:00 +08:00
root := f . dirPath ( dir )
var limit uint64 = 1000 // max number of objects per request
var itemsCount uint64 // number of items per page in response
var offset uint64 // for the next page of requests
2017-02-25 21:39:16 +08:00
for {
2018-11-20 01:27:00 +08:00
opts := & api . ResourceInfoRequestOptions {
Limit : limit ,
Offset : offset ,
}
2019-09-05 03:00:37 +08:00
info , err := f . readMetaDataForPath ( ctx , root , opts )
2018-11-20 01:27:00 +08:00
2017-02-25 21:39:16 +08:00
if err != nil {
2018-11-20 01:27:00 +08:00
if apiErr , ok := err . ( * api . ErrorResponse ) ; ok {
// does not exist
if apiErr . ErrorName == "DiskNotFoundError" {
return nil , fs . ErrorDirNotFound
}
2017-04-27 01:16:59 +08:00
}
2017-06-12 05:43:31 +08:00
return nil , err
2017-02-25 21:39:16 +08:00
}
2018-11-20 01:27:00 +08:00
itemsCount = uint64 ( len ( info . Embedded . Items ) )
2017-02-25 21:39:16 +08:00
2018-11-20 01:27:00 +08:00
if info . ResourceType == "dir" {
2017-02-25 21:39:16 +08:00
//list all subdirs
2018-11-20 01:27:00 +08:00
for _ , element := range info . Embedded . Items {
2020-01-15 01:33:35 +08:00
element . Name = f . opt . Enc . ToStandardName ( element . Name )
2017-02-25 21:39:16 +08:00
remote := path . Join ( dir , element . Name )
2019-09-05 03:00:37 +08:00
entry , err := f . itemToDirEntry ( ctx , remote , & element )
2017-06-12 05:43:31 +08:00
if err != nil {
return nil , err
}
if entry != nil {
entries = append ( entries , entry )
2016-04-22 03:06:21 +08:00
}
}
2018-11-20 01:27:00 +08:00
} else if info . ResourceType == "file" {
return nil , fs . ErrorIsFile
2016-04-22 03:06:21 +08:00
}
2017-02-25 21:39:16 +08:00
//offset for the next page of items
offset += itemsCount
//check if we reached end of list
if itemsCount < limit {
break
}
2016-04-22 03:06:21 +08:00
}
2015-12-07 12:01:03 +08:00
2018-11-20 01:27:00 +08:00
return entries , nil
2015-12-07 12:01:03 +08:00
}
2016-06-26 04:58:34 +08:00
// Return an Object from a path
2015-12-07 12:01:03 +08:00
//
2016-06-26 04:23:20 +08:00
// If it can't be found it returns the error fs.ErrorObjectNotFound.
2019-09-05 03:00:37 +08:00
func ( f * Fs ) newObjectWithInfo ( ctx context . Context , remote string , info * api . ResourceInfoResponse ) ( fs . Object , error ) {
2017-02-25 23:23:27 +08:00
o := & Object {
2015-12-07 12:01:03 +08:00
fs : f ,
remote : remote ,
}
2017-02-25 23:23:27 +08:00
var err error
2015-12-07 12:01:03 +08:00
if info != nil {
2017-02-25 19:09:57 +08:00
err = o . setMetaData ( info )
2015-12-07 12:01:03 +08:00
} else {
2019-09-05 03:00:37 +08:00
err = o . readMetaData ( ctx )
2018-11-20 01:27:00 +08:00
if apiErr , ok := err . ( * api . ErrorResponse ) ; ok {
// does not exist
if apiErr . ErrorName == "DiskNotFoundError" {
return nil , fs . ErrorObjectNotFound
}
}
2017-02-25 19:09:57 +08:00
}
if err != nil {
return nil , err
2015-12-07 12:01:03 +08:00
}
2016-06-26 04:23:20 +08:00
return o , nil
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
// NewObject finds the Object at remote. If it can't be found it
// returns the error fs.ErrorObjectNotFound.
2019-06-17 16:34:30 +08:00
func ( f * Fs ) NewObject ( ctx context . Context , remote string ) ( fs . Object , error ) {
2019-09-05 03:00:37 +08:00
return f . newObjectWithInfo ( ctx , remote , nil )
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
// Creates from the parameters passed in a half finished Object which
// must have setMetaData called on it
//
// Used to create new objects
func ( f * Fs ) createObject ( remote string , modTime time . Time , size int64 ) ( o * Object ) {
// Temporary Object under construction
o = & Object {
fs : f ,
remote : remote ,
size : size ,
modTime : modTime ,
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
return o
2015-12-07 12:01:03 +08:00
}
// Put the object
//
2022-08-05 23:35:41 +08:00
// Copy the reader in to the new object which is returned.
2015-12-07 12:01:03 +08:00
//
// The new object may have been created if an error is returned
2019-06-17 16:34:30 +08:00
func ( f * Fs ) Put ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) ( fs . Object , error ) {
o := f . createObject ( src . Remote ( ) , src . ModTime ( ctx ) , src . Size ( ) )
return o , o . Update ( ctx , in , src , options ... )
2015-12-07 12:01:03 +08:00
}
2017-08-19 20:07:23 +08:00
// PutStream uploads to the remote path with the modTime given of indeterminate size
2019-06-17 16:34:30 +08:00
func ( f * Fs ) PutStream ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) ( fs . Object , error ) {
return f . Put ( ctx , in , src , options ... )
2017-08-19 20:07:23 +08:00
}
2018-11-20 01:27:00 +08:00
// CreateDir makes a directory
2019-09-05 03:00:37 +08:00
func ( f * Fs ) CreateDir ( ctx context . Context , path string ) ( err error ) {
2018-11-20 01:27:00 +08:00
//fmt.Printf("CreateDir: %s\n", path)
var resp * http . Response
opts := rest . Opts {
Method : "PUT" ,
Path : "/resources" ,
Parameters : url . Values { } ,
NoResponse : true ,
}
2019-10-05 17:22:43 +08:00
// If creating a directory with a : use (undocumented) disk: prefix
2022-06-09 04:25:17 +08:00
if strings . ContainsRune ( path , ':' ) {
2019-10-05 17:22:43 +08:00
path = "disk:" + path
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "path" , f . opt . Enc . FromStandardPath ( path ) )
2018-11-20 01:27:00 +08:00
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
if err != nil {
2019-10-05 17:22:43 +08:00
// fmt.Printf("CreateDir %q Error: %s\n", path, err.Error())
2018-11-20 01:27:00 +08:00
return err
}
// fmt.Printf("...Id %q\n", *info.Id)
return nil
}
// This really needs improvement and especially proper error checking
// but Yandex does not publish a List of possible errors and when they're
// expected to occur.
2019-09-05 03:00:37 +08:00
func ( f * Fs ) mkDirs ( ctx context . Context , path string ) ( err error ) {
2018-11-20 01:27:00 +08:00
//trim filename from path
//dirString := strings.TrimSuffix(path, filepath.Base(path))
//trim "disk:" from path
dirString := strings . TrimPrefix ( path , "disk:" )
if dirString == "" {
return nil
}
2019-09-05 03:00:37 +08:00
if err = f . CreateDir ( ctx , dirString ) ; err != nil {
2018-11-20 01:27:00 +08:00
if apiErr , ok := err . ( * api . ErrorResponse ) ; ok {
2020-05-20 18:39:20 +08:00
// already exists
2018-11-20 01:27:00 +08:00
if apiErr . ErrorName != "DiskPathPointsToExistentDirectoryError" {
// 2 if it fails then create all directories in the path from root.
dirs := strings . Split ( dirString , "/" ) //path separator
var mkdirpath = "/" //path separator /
for _ , element := range dirs {
if element != "" {
2022-06-09 04:25:17 +08:00
mkdirpath += element + "/" //path separator /
_ = f . CreateDir ( ctx , mkdirpath ) // ignore errors while creating dirs
2018-11-20 01:27:00 +08:00
}
}
}
return nil
}
}
return err
}
2019-09-05 03:00:37 +08:00
func ( f * Fs ) mkParentDirs ( ctx context . Context , resPath string ) error {
2018-11-20 01:27:00 +08:00
// defer log.Trace(dirPath, "")("")
// chop off trailing / if it exists
2022-06-09 04:25:17 +08:00
parent := path . Dir ( strings . TrimSuffix ( resPath , "/" ) )
2018-11-20 01:27:00 +08:00
if parent == "." {
parent = ""
}
2019-09-05 03:00:37 +08:00
return f . mkDirs ( ctx , parent )
2018-11-20 01:27:00 +08:00
}
2015-12-07 12:01:03 +08:00
// Mkdir creates the container if it doesn't exist
2019-06-17 16:34:30 +08:00
func ( f * Fs ) Mkdir ( ctx context . Context , dir string ) error {
2018-11-20 01:27:00 +08:00
path := f . filePath ( dir )
2019-09-05 03:00:37 +08:00
return f . mkDirs ( ctx , path )
2018-11-20 01:27:00 +08:00
}
// waitForJob waits for the job with status in url to complete
2019-09-05 03:00:37 +08:00
func ( f * Fs ) waitForJob ( ctx context . Context , location string ) ( err error ) {
2018-11-20 01:27:00 +08:00
opts := rest . Opts {
RootURL : location ,
Method : "GET" ,
2016-11-26 05:52:43 +08:00
}
2021-03-01 20:05:36 +08:00
deadline := time . Now ( ) . Add ( f . ci . TimeoutOrInfinite ( ) )
2018-11-20 01:27:00 +08:00
for time . Now ( ) . Before ( deadline ) {
var resp * http . Response
var body [ ] byte
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
if fserrors . ContextError ( ctx , & err ) {
return false , err
}
2018-11-20 01:27:00 +08:00
if err != nil {
return fserrors . ShouldRetry ( err ) , err
}
body , err = rest . ReadBody ( resp )
return fserrors . ShouldRetry ( err ) , err
} )
if err != nil {
return err
}
// Try to decode the body first as an api.AsyncOperationStatus
var status api . AsyncStatus
err = json . Unmarshal ( body , & status )
if err != nil {
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "async status result not JSON: %q: %w" , body , err )
2018-11-20 01:27:00 +08:00
}
switch status . Status {
case "failure" :
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "async operation returned %q" , status . Status )
2018-11-20 01:27:00 +08:00
case "success" :
return nil
}
time . Sleep ( 1 * time . Second )
}
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "async operation didn't complete after %v" , f . ci . TimeoutOrInfinite ( ) )
2015-12-07 12:01:03 +08:00
}
2019-09-05 03:00:37 +08:00
func ( f * Fs ) delete ( ctx context . Context , path string , hardDelete bool ) ( err error ) {
2018-11-20 01:27:00 +08:00
opts := rest . Opts {
Method : "DELETE" ,
Path : "/resources" ,
Parameters : url . Values { } ,
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "path" , f . opt . Enc . FromStandardPath ( path ) )
2018-11-20 01:27:00 +08:00
opts . Parameters . Set ( "permanently" , strconv . FormatBool ( hardDelete ) )
var resp * http . Response
var body [ ] byte
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
if fserrors . ContextError ( ctx , & err ) {
return false , err
}
2018-11-20 01:27:00 +08:00
if err != nil {
return fserrors . ShouldRetry ( err ) , err
}
body , err = rest . ReadBody ( resp )
return fserrors . ShouldRetry ( err ) , err
} )
if err != nil {
return err
}
// if 202 Accepted it's an async operation we have to wait for it complete before retuning
if resp . StatusCode == 202 {
var info api . AsyncInfo
err = json . Unmarshal ( body , & info )
if err != nil {
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "async info result not JSON: %q: %w" , body , err )
2018-11-20 01:27:00 +08:00
}
2019-09-05 03:00:37 +08:00
return f . waitForJob ( ctx , info . HRef )
2018-11-20 01:27:00 +08:00
}
return nil
2015-12-07 12:01:03 +08:00
}
// purgeCheck remotes the root directory, if check is set then it
// refuses to do so if it has anything in
2019-09-05 03:00:37 +08:00
func ( f * Fs ) purgeCheck ( ctx context . Context , dir string , check bool ) error {
2018-11-20 01:27:00 +08:00
root := f . filePath ( dir )
2015-12-07 12:01:03 +08:00
if check {
//to comply with rclone logic we check if the directory is empty before delete.
//send request to get list of objects in this directory.
2019-09-05 03:00:37 +08:00
info , err := f . readMetaDataForPath ( ctx , root , & api . ResourceInfoRequestOptions { } )
2015-12-07 12:01:03 +08:00
if err != nil {
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "rmdir failed: %w" , err )
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
if len ( info . Embedded . Items ) != 0 {
return fs . ErrorDirectoryNotEmpty
2015-12-07 12:01:03 +08:00
}
}
//delete directory
2021-11-15 15:58:38 +08:00
return f . delete ( ctx , root , f . opt . HardDelete )
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
// Rmdir deletes the container
//
// Returns an error if it isn't empty
2019-06-17 16:34:30 +08:00
func ( f * Fs ) Rmdir ( ctx context . Context , dir string ) error {
2019-09-05 03:00:37 +08:00
return f . purgeCheck ( ctx , dir , true )
2015-12-07 12:01:03 +08:00
}
2020-06-05 05:25:14 +08:00
// Purge deletes all the files in the directory
2015-12-07 12:01:03 +08: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-05 05:25:14 +08:00
func ( f * Fs ) Purge ( ctx context . Context , dir string ) error {
return f . purgeCheck ( ctx , dir , false )
2015-12-07 12:01:03 +08:00
}
2019-02-08 01:41:17 +08:00
// copyOrMoves copies or moves directories or files depending on the method parameter
2019-09-05 03:00:37 +08:00
func ( f * Fs ) copyOrMove ( ctx context . Context , method , src , dst string , overwrite bool ) ( err error ) {
2018-11-20 01:27:00 +08:00
opts := rest . Opts {
Method : "POST" ,
Path : "/resources/" + method ,
Parameters : url . Values { } ,
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "from" , f . opt . Enc . FromStandardPath ( src ) )
opts . Parameters . Set ( "path" , f . opt . Enc . FromStandardPath ( dst ) )
2018-11-20 01:27:00 +08:00
opts . Parameters . Set ( "overwrite" , strconv . FormatBool ( overwrite ) )
var resp * http . Response
var body [ ] byte
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
if fserrors . ContextError ( ctx , & err ) {
return false , err
}
2018-11-20 01:27:00 +08:00
if err != nil {
return fserrors . ShouldRetry ( err ) , err
}
body , err = rest . ReadBody ( resp )
return fserrors . ShouldRetry ( err ) , err
} )
if err != nil {
return err
}
// if 202 Accepted it's an async operation we have to wait for it complete before retuning
if resp . StatusCode == 202 {
var info api . AsyncInfo
err = json . Unmarshal ( body , & info )
if err != nil {
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "async info result not JSON: %q: %w" , body , err )
2018-11-20 01:27:00 +08:00
}
2019-09-05 03:00:37 +08:00
return f . waitForJob ( ctx , info . HRef )
2018-11-20 01:27:00 +08:00
}
return nil
}
2020-10-14 05:43:40 +08:00
// Copy src to this remote using server-side copy operations.
2018-11-20 01:27:00 +08:00
//
2022-08-05 23:35:41 +08:00
// This is stored with the remote path given.
2018-11-20 01:27:00 +08:00
//
2022-08-05 23:35:41 +08:00
// It returns the destination Object and a possible error.
2018-11-20 01:27:00 +08:00
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantCopy
2024-11-08 22:01:51 +08:00
func ( f * Fs ) Copy ( ctx context . Context , src fs . Object , remote string ) ( dst fs . Object , err error ) {
2018-11-20 01:27:00 +08:00
srcObj , ok := src . ( * Object )
if ! ok {
fs . Debugf ( src , "Can't copy - not same remote type" )
return nil , fs . ErrorCantCopy
}
dstPath := f . filePath ( remote )
2024-11-08 22:01:51 +08:00
err = f . mkParentDirs ( ctx , dstPath )
2018-11-20 01:27:00 +08:00
if err != nil {
return nil , err
}
2024-11-08 22:01:51 +08:00
// Find and remove existing object
//
// Note that the overwrite flag doesn't seem to work for server side copy
cleanup , err := operations . RemoveExisting ( ctx , f , remote , "server side copy" )
if err != nil {
return nil , err
}
defer cleanup ( & err )
err = f . copyOrMove ( ctx , "copy" , srcObj . filePath ( ) , dstPath , false )
2018-11-20 01:27:00 +08:00
if err != nil {
2021-11-04 18:12:57 +08:00
return nil , fmt . Errorf ( "couldn't copy file: %w" , err )
2018-11-20 01:27:00 +08:00
}
2019-06-17 16:34:30 +08:00
return f . NewObject ( ctx , remote )
2018-11-20 01:27:00 +08:00
}
2020-10-14 05:43:40 +08:00
// Move src to this remote using server-side move operations.
2018-11-20 01:27:00 +08:00
//
2022-08-05 23:35:41 +08:00
// This is stored with the remote path given.
2018-11-20 01:27:00 +08:00
//
2022-08-05 23:35:41 +08:00
// It returns the destination Object and a possible error.
2018-11-20 01:27:00 +08:00
//
// Will only be called if src.Fs().Name() == f.Name()
//
// If it isn't possible then return fs.ErrorCantMove
2019-06-17 16:34:30 +08:00
func ( f * Fs ) Move ( ctx context . Context , src fs . Object , remote string ) ( fs . Object , error ) {
2018-11-20 01:27:00 +08:00
srcObj , ok := src . ( * Object )
if ! ok {
fs . Debugf ( src , "Can't move - not same remote type" )
return nil , fs . ErrorCantMove
}
dstPath := f . filePath ( remote )
2019-09-05 03:00:37 +08:00
err := f . mkParentDirs ( ctx , dstPath )
2018-11-20 01:27:00 +08:00
if err != nil {
return nil , err
}
2019-09-05 03:00:37 +08:00
err = f . copyOrMove ( ctx , "move" , srcObj . filePath ( ) , dstPath , false )
2018-11-20 01:27:00 +08:00
if err != nil {
2021-11-04 18:12:57 +08:00
return nil , fmt . Errorf ( "couldn't move file: %w" , err )
2018-11-20 01:27:00 +08:00
}
2019-06-17 16:34:30 +08:00
return f . NewObject ( ctx , remote )
2018-11-20 01:27:00 +08:00
}
// DirMove moves src, srcRemote to this remote at dstRemote
2020-10-14 05:43:40 +08:00
// using server-side move operations.
2018-11-20 01:27:00 +08: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 16:34:30 +08:00
func ( f * Fs ) DirMove ( ctx context . Context , src fs . Fs , srcRemote , dstRemote string ) error {
2018-11-20 01:27:00 +08:00
srcFs , ok := src . ( * Fs )
if ! ok {
fs . Debugf ( srcFs , "Can't move directory - not same remote type" )
return fs . ErrorCantDirMove
}
srcPath := path . Join ( srcFs . diskRoot , srcRemote )
dstPath := f . dirPath ( dstRemote )
//fmt.Printf("Move src: %s (FullPath: %s), dst: %s (FullPath: %s)\n", srcRemote, srcPath, dstRemote, dstPath)
// Refuse to move to or from the root
if srcPath == "disk:/" || dstPath == "disk:/" {
fs . Debugf ( src , "DirMove error: Can't move root" )
return errors . New ( "can't move root directory" )
}
2019-09-05 03:00:37 +08:00
err := f . mkParentDirs ( ctx , dstPath )
2018-11-20 01:27:00 +08:00
if err != nil {
return err
}
2019-09-05 03:00:37 +08:00
_ , err = f . readMetaDataForPath ( ctx , dstPath , & api . ResourceInfoRequestOptions { } )
2022-07-02 22:33:04 +08:00
if apiErr , ok := err . ( * api . ErrorResponse ) ; ok {
if apiErr . ErrorName != "DiskNotFoundError" {
return err
}
2018-11-20 01:27:00 +08:00
} else if err != nil {
return err
} else {
return fs . ErrorDirExists
}
2019-09-05 03:00:37 +08:00
err = f . copyOrMove ( ctx , "move" , srcPath , dstPath , false )
2018-11-20 01:27:00 +08:00
if err != nil {
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "couldn't move directory: %w" , err )
2018-11-20 01:27:00 +08:00
}
return nil
}
// PublicLink generates a public link to the remote path (usually readable by anyone)
2020-06-01 05:18:01 +08:00
func ( f * Fs ) PublicLink ( ctx context . Context , remote string , expire fs . Duration , unlink bool ) ( link string , err error ) {
2018-11-20 01:27:00 +08:00
var path string
2020-06-01 05:18:01 +08:00
if unlink {
2018-11-20 01:27:00 +08:00
path = "/resources/unpublish"
} else {
path = "/resources/publish"
}
opts := rest . Opts {
Method : "PUT" ,
2020-01-15 01:33:35 +08:00
Path : f . opt . Enc . FromStandardPath ( path ) ,
2018-11-20 01:27:00 +08:00
Parameters : url . Values { } ,
NoResponse : true ,
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "path" , f . opt . Enc . FromStandardPath ( f . filePath ( remote ) ) )
2018-11-20 01:27:00 +08:00
var resp * http . Response
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
if apiErr , ok := err . ( * api . ErrorResponse ) ; ok {
// does not exist
if apiErr . ErrorName == "DiskNotFoundError" {
return "" , fs . ErrorObjectNotFound
}
}
if err != nil {
2020-06-01 05:18:01 +08:00
if unlink {
2021-11-04 18:12:57 +08:00
return "" , fmt . Errorf ( "couldn't remove public link: %w" , err )
2018-11-20 01:27:00 +08:00
}
2021-11-04 18:12:57 +08:00
return "" , fmt . Errorf ( "couldn't create public link: %w" , err )
2018-11-20 01:27:00 +08:00
}
2019-09-05 03:00:37 +08:00
info , err := f . readMetaDataForPath ( ctx , f . filePath ( remote ) , & api . ResourceInfoRequestOptions { } )
2018-11-20 01:27:00 +08:00
if err != nil {
return "" , err
}
if info . PublicURL == "" {
return "" , errors . New ( "couldn't create public link - no link path received" )
}
return info . PublicURL , nil
}
2017-09-08 01:10:39 +08:00
// CleanUp permanently deletes all trashed files/folders
2019-06-17 16:34:30 +08:00
func ( f * Fs ) CleanUp ( ctx context . Context ) ( err error ) {
2018-11-20 01:27:00 +08:00
var resp * http . Response
opts := rest . Opts {
Method : "DELETE" ,
Path : "/trash/resources" ,
NoResponse : true ,
}
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
return err
2017-09-08 01:10:39 +08:00
}
2018-11-20 01:27:00 +08:00
// About gets quota information
2019-06-17 16:34:30 +08:00
func ( f * Fs ) About ( ctx context . Context ) ( * fs . Usage , error ) {
2018-11-20 01:27:00 +08:00
opts := rest . Opts {
Method : "GET" ,
Path : "/" ,
}
var resp * http . Response
var info api . DiskInfo
var err error
err = f . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = f . srv . CallJSON ( ctx , & opts , nil , & info )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
if err != nil {
return nil , err
}
usage := & fs . Usage {
Total : fs . NewUsageValue ( info . TotalSpace ) ,
Used : fs . NewUsageValue ( info . UsedSpace ) ,
Free : fs . NewUsageValue ( info . TotalSpace - info . UsedSpace ) ,
}
return usage , nil
2016-01-11 20:39:33 +08:00
}
2015-12-07 12:01:03 +08:00
// ------------------------------------------------------------
// Fs returns the parent Fs
2016-02-18 19:35:25 +08:00
func ( o * Object ) Fs ( ) fs . Info {
2015-12-07 12:01:03 +08:00
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
}
2018-11-20 01:27:00 +08:00
// Returns the full remote path for the object
func ( o * Object ) filePath ( ) string {
return o . fs . filePath ( o . remote )
}
// setMetaData sets the fs data from a storage.Object
func ( o * Object ) setMetaData ( info * api . ResourceInfoResponse ) ( err error ) {
o . hasMetaData = true
o . size = info . Size
o . md5sum = info . Md5
o . mimeType = info . MimeType
var modTimeString string
modTimeObj , ok := info . CustomProperties [ "rclone_modified" ]
if ok {
// read modTime from rclone_modified custom_property of object
modTimeString , ok = modTimeObj . ( string )
2016-01-11 20:39:33 +08:00
}
2018-11-20 01:27:00 +08:00
if ! ok {
// read modTime from Modified property of object as a fallback
modTimeString = info . Modified
}
t , err := time . Parse ( time . RFC3339Nano , modTimeString )
if err != nil {
2021-11-04 18:12:57 +08:00
return fmt . Errorf ( "failed to parse modtime from %q: %w" , modTimeString , err )
2018-11-20 01:27:00 +08:00
}
o . modTime = t
return nil
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
// readMetaData reads ands sets the new metadata for a storage.Object
2019-09-05 03:00:37 +08:00
func ( o * Object ) readMetaData ( ctx context . Context ) ( err error ) {
2018-11-20 01:27:00 +08:00
if o . hasMetaData {
return nil
}
2019-09-05 03:00:37 +08:00
info , err := o . fs . readMetaDataForPath ( ctx , o . filePath ( ) , & api . ResourceInfoRequestOptions { } )
2018-11-20 01:27:00 +08:00
if err != nil {
return err
}
2021-09-06 20:54:08 +08:00
if info . ResourceType == "dir" {
return fs . ErrorIsDir
} else if info . ResourceType != "file" {
2018-11-20 01:27:00 +08:00
return fs . ErrorNotAFile
}
return o . setMetaData ( info )
2015-12-07 12:01:03 +08: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
2019-06-17 16:34:30 +08:00
func ( o * Object ) ModTime ( ctx context . Context ) time . Time {
2019-09-05 03:00:37 +08:00
err := o . readMetaData ( ctx )
2015-12-07 12:01:03 +08:00
if err != nil {
2017-02-09 19:01:20 +08:00
fs . Logf ( o , "Failed to read metadata: %v" , err )
2015-12-07 12:01:03 +08:00
return time . Now ( )
}
return o . modTime
}
2018-11-20 01:27:00 +08:00
// Size returns the size of an object in bytes
func ( o * Object ) Size ( ) int64 {
2019-09-05 03:00:37 +08:00
ctx := context . TODO ( )
err := o . readMetaData ( ctx )
2018-11-20 01:27:00 +08:00
if err != nil {
fs . Logf ( o , "Failed to read metadata: %v" , err )
return 0
}
return o . size
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
// Hash returns the Md5sum of an object returning a lowercase hex string
2019-06-17 16:34:30 +08:00
func ( o * Object ) Hash ( ctx context . Context , t hash . Type ) ( string , error ) {
2018-11-20 01:27:00 +08:00
if t != hash . MD5 {
return "" , hash . ErrUnsupported
}
return o . md5sum , nil
}
// Storable returns whether this object is storable
func ( o * Object ) Storable ( ) bool {
return true
}
2019-09-05 03:00:37 +08:00
func ( o * Object ) setCustomProperty ( ctx context . Context , property string , value string ) ( err error ) {
2018-11-20 01:27:00 +08:00
var resp * http . Response
opts := rest . Opts {
Method : "PATCH" ,
Path : "/resources" ,
Parameters : url . Values { } ,
NoResponse : true ,
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "path" , o . fs . opt . Enc . FromStandardPath ( o . filePath ( ) ) )
2018-11-20 01:27:00 +08:00
rcm := map [ string ] interface { } {
property : value ,
}
cpr := api . CustomPropertyResponse { CustomProperties : rcm }
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , & cpr , nil )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
return err
2015-12-07 12:01:03 +08:00
}
// SetModTime sets the modification time of the local fs object
//
// Commits the datastore
2019-06-17 16:34:30 +08:00
func ( o * Object ) SetModTime ( ctx context . Context , modTime time . Time ) error {
2016-09-22 05:13:24 +08:00
// set custom_property 'rclone_modified' of object to modTime
2019-09-05 03:00:37 +08:00
err := o . setCustomProperty ( ctx , "rclone_modified" , modTime . Format ( time . RFC3339Nano ) )
2016-09-22 05:13:24 +08:00
if err != nil {
return err
}
o . modTime = modTime
return nil
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
// Open an object for read
2019-06-17 16:34:30 +08:00
func ( o * Object ) Open ( ctx context . Context , options ... fs . OpenOption ) ( in io . ReadCloser , err error ) {
2018-11-20 01:27:00 +08:00
// prepare download
var resp * http . Response
var dl api . AsyncInfo
opts := rest . Opts {
Method : "GET" ,
Path : "/resources/download" ,
Parameters : url . Values { } ,
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "path" , o . fs . opt . Enc . FromStandardPath ( o . filePath ( ) ) )
2018-11-20 01:27:00 +08:00
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , nil , & dl )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
if err != nil {
return nil , err
}
// perform the download
opts = rest . Opts {
RootURL : dl . HRef ,
Method : "GET" ,
Options : options ,
}
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = o . fs . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
if err != nil {
return nil , err
}
return resp . Body , err
2015-12-07 12:01:03 +08:00
}
2020-03-22 05:56:11 +08:00
func ( o * Object ) upload ( ctx context . Context , in io . Reader , overwrite bool , mimeType string , options ... fs . OpenOption ) ( err error ) {
2018-11-20 01:27:00 +08:00
// prepare upload
var resp * http . Response
var ur api . AsyncInfo
opts := rest . Opts {
Method : "GET" ,
Path : "/resources/upload" ,
Parameters : url . Values { } ,
2020-03-22 05:56:11 +08:00
Options : options ,
2018-11-20 01:27:00 +08:00
}
2020-01-15 01:33:35 +08:00
opts . Parameters . Set ( "path" , o . fs . opt . Enc . FromStandardPath ( o . filePath ( ) ) )
2018-11-20 01:27:00 +08:00
opts . Parameters . Set ( "overwrite" , strconv . FormatBool ( overwrite ) )
err = o . fs . pacer . Call ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = o . fs . srv . CallJSON ( ctx , & opts , nil , & ur )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
if err != nil {
return err
}
// perform the actual upload
opts = rest . Opts {
RootURL : ur . HRef ,
Method : "PUT" ,
ContentType : mimeType ,
Body : in ,
NoResponse : true ,
}
2023-06-10 01:03:05 +08:00
err = o . fs . pacer . CallNoRetry ( func ( ) ( bool , error ) {
2019-09-05 03:00:37 +08:00
resp , err = o . fs . srv . Call ( ctx , & opts )
2021-03-11 22:44:01 +08:00
return shouldRetry ( ctx , resp , err )
2018-11-20 01:27:00 +08:00
} )
return err
2015-12-07 12:01:03 +08:00
}
// Update the already existing object
//
2022-08-05 23:35:41 +08:00
// Copy the reader into the object updating modTime and size.
2015-12-07 12:01:03 +08:00
//
// The new object may have been created if an error is returned
2019-06-17 16:34:30 +08:00
func ( o * Object ) Update ( ctx context . Context , in io . Reader , src fs . ObjectInfo , options ... fs . OpenOption ) error {
2018-11-20 01:27:00 +08:00
in1 := readers . NewCountingReader ( in )
2019-06-17 16:34:30 +08:00
modTime := src . ModTime ( ctx )
2018-11-20 01:27:00 +08:00
remote := o . filePath ( )
2016-02-18 19:35:25 +08:00
2015-12-07 12:01:03 +08:00
//create full path to file before upload.
2019-09-05 03:00:37 +08:00
err := o . fs . mkParentDirs ( ctx , remote )
2018-11-20 01:27:00 +08:00
if err != nil {
return err
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
2015-12-07 12:01:03 +08:00
//upload file
2020-03-22 05:56:11 +08:00
err = o . upload ( ctx , in1 , true , fs . MimeType ( ctx , src ) , options ... )
2018-11-20 01:27:00 +08:00
if err != nil {
return err
2015-12-07 12:01:03 +08:00
}
2019-02-08 01:41:17 +08:00
//if file uploaded successfully then return metadata
2018-11-20 01:27:00 +08:00
o . modTime = modTime
o . md5sum = "" // according to unit tests after put the md5 is empty.
o . size = int64 ( in1 . BytesRead ( ) ) // better solution o.readMetaData() ?
//and set modTime of uploaded file
2019-06-17 16:34:30 +08:00
err = o . SetModTime ( ctx , modTime )
2015-12-07 12:01:03 +08:00
2018-11-20 01:27:00 +08:00
return err
2015-12-07 12:01:03 +08:00
}
2018-11-20 01:27:00 +08:00
// Remove an object
2019-06-17 16:34:30 +08:00
func ( o * Object ) Remove ( ctx context . Context ) error {
2021-11-15 15:58:38 +08:00
return o . fs . delete ( ctx , o . filePath ( ) , o . fs . opt . HardDelete )
2015-12-07 12:01:03 +08:00
}
2016-09-22 05:13:24 +08:00
// MimeType of an Object if known, "" otherwise
2019-06-17 16:34:30 +08:00
func ( o * Object ) MimeType ( ctx context . Context ) string {
2016-09-22 05:13:24 +08:00
return o . mimeType
}
2015-12-07 12:01:03 +08:00
// Check the interfaces are satisfied
var (
2018-11-20 01:27:00 +08:00
_ fs . Fs = ( * Fs ) ( nil )
_ fs . Purger = ( * Fs ) ( nil )
_ fs . Copier = ( * Fs ) ( nil )
_ fs . Mover = ( * Fs ) ( nil )
_ fs . DirMover = ( * Fs ) ( nil )
_ fs . PublicLinker = ( * Fs ) ( nil )
_ fs . CleanUpper = ( * Fs ) ( nil )
_ fs . Abouter = ( * Fs ) ( nil )
_ fs . Object = ( * Object ) ( nil )
_ fs . MimeTyper = ( * Object ) ( nil )
2015-12-07 12:01:03 +08:00
)