backend: adjust backends to have encoding parameter

Fixes #3761
Fixes #3836
Fixes #3841
This commit is contained in:
Nick Craig-Wood 2020-01-14 17:33:35 +00:00
parent 0a5c83ece1
commit 3c620d521d
35 changed files with 667 additions and 514 deletions

View File

@ -33,13 +33,13 @@ import (
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const ( const (
enc = encodings.AmazonCloudDrive
folderKind = "FOLDER" folderKind = "FOLDER"
fileKind = "FILE" fileKind = "FILE"
statusAvailable = "AVAILABLE" statusAvailable = "AVAILABLE"
@ -137,6 +137,11 @@ which downloads the file through a temporary URL directly from the
underlying S3 storage.`, underlying S3 storage.`,
Default: defaultTempLinkThreshold, Default: defaultTempLinkThreshold,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.AmazonCloudDrive,
}}, }},
}) })
} }
@ -146,6 +151,7 @@ type Options struct {
Checkpoint string `config:"checkpoint"` Checkpoint string `config:"checkpoint"`
UploadWaitPerGB fs.Duration `config:"upload_wait_per_gb"` UploadWaitPerGB fs.Duration `config:"upload_wait_per_gb"`
TempLinkThreshold fs.SizeSuffix `config:"templink_threshold"` TempLinkThreshold fs.SizeSuffix `config:"templink_threshold"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote acd server // Fs represents a remote acd server
@ -386,7 +392,7 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut strin
var resp *http.Response var resp *http.Response
var subFolder *acd.Folder var subFolder *acd.Folder
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
subFolder, resp, err = folder.GetFolder(enc.FromStandardName(leaf)) subFolder, resp, err = folder.GetFolder(f.opt.Enc.FromStandardName(leaf))
return f.shouldRetry(resp, err) return f.shouldRetry(resp, err)
}) })
if err != nil { if err != nil {
@ -413,7 +419,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string,
var resp *http.Response var resp *http.Response
var info *acd.Folder var info *acd.Folder
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
info, resp, err = folder.CreateFolder(enc.FromStandardName(leaf)) info, resp, err = folder.CreateFolder(f.opt.Enc.FromStandardName(leaf))
return f.shouldRetry(resp, err) return f.shouldRetry(resp, err)
}) })
if err != nil { if err != nil {
@ -481,7 +487,7 @@ func (f *Fs) listAll(dirID string, title string, directoriesOnly bool, filesOnly
if !hasValidParent { if !hasValidParent {
continue continue
} }
*node.Name = enc.ToStandardName(*node.Name) *node.Name = f.opt.Enc.ToStandardName(*node.Name)
// Store the nodes up in case we have to retry the listing // Store the nodes up in case we have to retry the listing
out = append(out, node) out = append(out, node)
} }
@ -671,7 +677,7 @@ func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options .
err = f.pacer.CallNoRetry(func() (bool, error) { err = f.pacer.CallNoRetry(func() (bool, error) {
start := time.Now() start := time.Now()
f.tokenRenewer.Start() f.tokenRenewer.Start()
info, resp, err = folder.Put(in, enc.FromStandardName(leaf)) info, resp, err = folder.Put(in, f.opt.Enc.FromStandardName(leaf))
f.tokenRenewer.Stop() f.tokenRenewer.Stop()
var ok bool var ok bool
ok, info, err = f.checkUpload(ctx, resp, in, src, info, err, time.Since(start)) ok, info, err = f.checkUpload(ctx, resp, in, src, info, err, time.Since(start))
@ -1041,7 +1047,7 @@ func (o *Object) readMetaData(ctx context.Context) (err error) {
var resp *http.Response var resp *http.Response
var info *acd.File var info *acd.File
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
info, resp, err = folder.GetFile(enc.FromStandardName(leaf)) info, resp, err = folder.GetFile(o.fs.opt.Enc.FromStandardName(leaf))
return o.fs.shouldRetry(resp, err) return o.fs.shouldRetry(resp, err)
}) })
if err != nil { if err != nil {
@ -1161,7 +1167,7 @@ func (f *Fs) restoreNode(info *acd.Node) (newInfo *acd.Node, err error) {
func (f *Fs) renameNode(info *acd.Node, newName string) (newInfo *acd.Node, err error) { func (f *Fs) renameNode(info *acd.Node, newName string) (newInfo *acd.Node, err error) {
var resp *http.Response var resp *http.Response
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
newInfo, resp, err = info.Rename(enc.FromStandardName(newName)) newInfo, resp, err = info.Rename(f.opt.Enc.FromStandardName(newName))
return f.shouldRetry(resp, err) return f.shouldRetry(resp, err)
}) })
return newInfo, err return newInfo, err
@ -1357,7 +1363,7 @@ func (f *Fs) changeNotifyRunner(notifyFunc func(string, fs.EntryType), checkpoin
if len(node.Parents) > 0 { if len(node.Parents) > 0 {
if path, ok := f.dirCache.GetInv(node.Parents[0]); ok { if path, ok := f.dirCache.GetInv(node.Parents[0]); ok {
// and append the drive file name to compute the full file name // and append the drive file name to compute the full file name
name := enc.ToStandardName(*node.Name) name := f.opt.Enc.ToStandardName(*node.Name)
if len(path) > 0 { if len(path) > 0 {
path = path + "/" + name path = path + "/" + name
} else { } else {

View File

@ -26,6 +26,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting" "github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
@ -34,6 +35,7 @@ import (
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/bucket" "github.com/rclone/rclone/lib/bucket"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
) )
@ -61,8 +63,6 @@ const (
emulatorBlobEndpoint = "http://127.0.0.1:10000/devstoreaccount1" emulatorBlobEndpoint = "http://127.0.0.1:10000/devstoreaccount1"
) )
const enc = encodings.AzureBlob
// Register with Fs // Register with Fs
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
@ -127,6 +127,11 @@ If blobs are in "archive tier" at remote, trying to perform data transfer
operations from remote will not be allowed. User should first restore by operations from remote will not be allowed. User should first restore by
tiering blob to "Hot" or "Cool".`, tiering blob to "Hot" or "Cool".`,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.AzureBlob,
}}, }},
}) })
} }
@ -142,6 +147,7 @@ type Options struct {
ListChunkSize uint `config:"list_chunk"` ListChunkSize uint `config:"list_chunk"`
AccessTier string `config:"access_tier"` AccessTier string `config:"access_tier"`
UseEmulator bool `config:"use_emulator"` UseEmulator bool `config:"use_emulator"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote azure server // Fs represents a remote azure server
@ -212,7 +218,7 @@ func parsePath(path string) (root string) {
// relative to f.root // relative to f.root
func (f *Fs) split(rootRelativePath string) (containerName, containerPath string) { func (f *Fs) split(rootRelativePath string) (containerName, containerPath string) {
containerName, containerPath = bucket.Split(path.Join(f.root, rootRelativePath)) containerName, containerPath = bucket.Split(path.Join(f.root, rootRelativePath))
return enc.FromStandardName(containerName), enc.FromStandardPath(containerPath) return f.opt.Enc.FromStandardName(containerName), f.opt.Enc.FromStandardPath(containerPath)
} }
// split returns container and containerPath from the object // split returns container and containerPath from the object
@ -588,7 +594,7 @@ func (f *Fs) list(ctx context.Context, container, directory, prefix string, addC
// if prefix != "" && !strings.HasPrefix(file.Name, prefix) { // if prefix != "" && !strings.HasPrefix(file.Name, prefix) {
// return nil // return nil
// } // }
remote := enc.ToStandardPath(file.Name) remote := f.opt.Enc.ToStandardPath(file.Name)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Debugf(f, "Odd name received %q", remote) fs.Debugf(f, "Odd name received %q", remote)
continue continue
@ -609,7 +615,7 @@ func (f *Fs) list(ctx context.Context, container, directory, prefix string, addC
// Send the subdirectories // Send the subdirectories
for _, remote := range response.Segment.BlobPrefixes { for _, remote := range response.Segment.BlobPrefixes {
remote := strings.TrimRight(remote.Name, "/") remote := strings.TrimRight(remote.Name, "/")
remote = enc.ToStandardPath(remote) remote = f.opt.Enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Debugf(f, "Odd directory name received %q", remote) fs.Debugf(f, "Odd directory name received %q", remote)
continue continue
@ -673,7 +679,7 @@ func (f *Fs) listContainers(ctx context.Context) (entries fs.DirEntries, err err
return entries, nil return entries, nil
} }
err = f.listContainersToFn(func(container *azblob.ContainerItem) error { err = f.listContainersToFn(func(container *azblob.ContainerItem) error {
d := fs.NewDir(enc.ToStandardName(container.Name), container.Properties.LastModified) d := fs.NewDir(f.opt.Enc.ToStandardName(container.Name), container.Properties.LastModified)
f.cache.MarkOK(container.Name) f.cache.MarkOK(container.Name)
entries = append(entries, d) entries = append(entries, d)
return nil return nil

View File

@ -23,6 +23,7 @@ import (
"github.com/rclone/rclone/backend/b2/api" "github.com/rclone/rclone/backend/b2/api"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting" "github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
@ -31,12 +32,11 @@ import (
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/bucket" "github.com/rclone/rclone/lib/bucket"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
) )
const enc = encodings.B2
const ( const (
defaultEndpoint = "https://api.backblazeb2.com" defaultEndpoint = "https://api.backblazeb2.com"
headerPrefix = "x-bz-info-" // lower case as that is what the server returns headerPrefix = "x-bz-info-" // lower case as that is what the server returns
@ -146,6 +146,11 @@ The duration before the download authorization token will expire.
The minimum value is 1 second. The maximum value is one week.`, The minimum value is 1 second. The maximum value is one week.`,
Default: fs.Duration(7 * 24 * time.Hour), Default: fs.Duration(7 * 24 * time.Hour),
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.B2,
}}, }},
}) })
} }
@ -163,6 +168,7 @@ type Options struct {
DisableCheckSum bool `config:"disable_checksum"` DisableCheckSum bool `config:"disable_checksum"`
DownloadURL string `config:"download_url"` DownloadURL string `config:"download_url"`
DownloadAuthorizationDuration fs.Duration `config:"download_auth_duration"` DownloadAuthorizationDuration fs.Duration `config:"download_auth_duration"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote b2 server // Fs represents a remote b2 server
@ -402,7 +408,7 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
} }
// If this is a key limited to a single bucket, it must exist already // If this is a key limited to a single bucket, it must exist already
if f.rootBucket != "" && f.info.Allowed.BucketID != "" { if f.rootBucket != "" && f.info.Allowed.BucketID != "" {
allowedBucket := enc.ToStandardName(f.info.Allowed.BucketName) allowedBucket := f.opt.Enc.ToStandardName(f.info.Allowed.BucketName)
if allowedBucket == "" { if allowedBucket == "" {
return nil, errors.New("bucket that application key is restricted to no longer exists") return nil, errors.New("bucket that application key is restricted to no longer exists")
} }
@ -623,11 +629,11 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
var request = api.ListFileNamesRequest{ var request = api.ListFileNamesRequest{
BucketID: bucketID, BucketID: bucketID,
MaxFileCount: chunkSize, MaxFileCount: chunkSize,
Prefix: enc.FromStandardPath(directory), Prefix: f.opt.Enc.FromStandardPath(directory),
Delimiter: delimiter, Delimiter: delimiter,
} }
if directory != "" { if directory != "" {
request.StartFileName = enc.FromStandardPath(directory) request.StartFileName = f.opt.Enc.FromStandardPath(directory)
} }
opts := rest.Opts{ opts := rest.Opts{
Method: "POST", Method: "POST",
@ -647,7 +653,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
} }
for i := range response.Files { for i := range response.Files {
file := &response.Files[i] file := &response.Files[i]
file.Name = enc.ToStandardPath(file.Name) file.Name = f.opt.Enc.ToStandardPath(file.Name)
// Finish if file name no longer has prefix // Finish if file name no longer has prefix
if prefix != "" && !strings.HasPrefix(file.Name, prefix) { if prefix != "" && !strings.HasPrefix(file.Name, prefix) {
return nil return nil
@ -848,7 +854,7 @@ func (f *Fs) listBucketsToFn(ctx context.Context, fn listBucketFn) error {
f._bucketType = make(map[string]string, 1) f._bucketType = make(map[string]string, 1)
for i := range response.Buckets { for i := range response.Buckets {
bucket := &response.Buckets[i] bucket := &response.Buckets[i]
bucket.Name = enc.ToStandardName(bucket.Name) bucket.Name = f.opt.Enc.ToStandardName(bucket.Name)
f.cache.MarkOK(bucket.Name) f.cache.MarkOK(bucket.Name)
f._bucketID[bucket.Name] = bucket.ID f._bucketID[bucket.Name] = bucket.ID
f._bucketType[bucket.Name] = bucket.Type f._bucketType[bucket.Name] = bucket.Type
@ -970,7 +976,7 @@ func (f *Fs) makeBucket(ctx context.Context, bucket string) error {
} }
var request = api.CreateBucketRequest{ var request = api.CreateBucketRequest{
AccountID: f.info.AccountID, AccountID: f.info.AccountID,
Name: enc.FromStandardName(bucket), Name: f.opt.Enc.FromStandardName(bucket),
Type: "allPrivate", Type: "allPrivate",
} }
var response api.Bucket var response api.Bucket
@ -1054,7 +1060,7 @@ func (f *Fs) hide(ctx context.Context, bucket, bucketPath string) error {
} }
var request = api.HideFileRequest{ var request = api.HideFileRequest{
BucketID: bucketID, BucketID: bucketID,
Name: enc.FromStandardPath(bucketPath), Name: f.opt.Enc.FromStandardPath(bucketPath),
} }
var response api.File var response api.File
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
@ -1082,7 +1088,7 @@ func (f *Fs) deleteByID(ctx context.Context, ID, Name string) error {
} }
var request = api.DeleteFileRequest{ var request = api.DeleteFileRequest{
ID: ID, ID: ID,
Name: enc.FromStandardPath(Name), Name: f.opt.Enc.FromStandardPath(Name),
} }
var response api.File var response api.File
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
@ -1220,7 +1226,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
} }
var request = api.CopyFileRequest{ var request = api.CopyFileRequest{
SourceID: srcObj.id, SourceID: srcObj.id,
Name: enc.FromStandardPath(dstPath), Name: f.opt.Enc.FromStandardPath(dstPath),
MetadataDirective: "COPY", MetadataDirective: "COPY",
DestBucketID: destBucketID, DestBucketID: destBucketID,
} }
@ -1268,7 +1274,7 @@ func (f *Fs) getDownloadAuthorization(ctx context.Context, bucket, remote string
} }
var request = api.GetDownloadAuthorizationRequest{ var request = api.GetDownloadAuthorizationRequest{
BucketID: bucketID, BucketID: bucketID,
FileNamePrefix: enc.FromStandardPath(path.Join(f.root, remote)), FileNamePrefix: f.opt.Enc.FromStandardPath(path.Join(f.root, remote)),
ValidDurationInSeconds: validDurationInSeconds, ValidDurationInSeconds: validDurationInSeconds,
} }
var response api.GetDownloadAuthorizationResponse var response api.GetDownloadAuthorizationResponse
@ -1509,7 +1515,7 @@ func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
} }
var request = api.CopyFileRequest{ var request = api.CopyFileRequest{
SourceID: o.id, SourceID: o.id,
Name: enc.FromStandardPath(bucketPath), // copy to same name Name: o.fs.opt.Enc.FromStandardPath(bucketPath), // copy to same name
MetadataDirective: "REPLACE", MetadataDirective: "REPLACE",
ContentType: info.ContentType, ContentType: info.ContentType,
Info: info.Info, Info: info.Info,
@ -1611,7 +1617,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
opts.Path += "/b2api/v1/b2_download_file_by_id?fileId=" + urlEncode(o.id) opts.Path += "/b2api/v1/b2_download_file_by_id?fileId=" + urlEncode(o.id)
} else { } else {
bucket, bucketPath := o.split() bucket, bucketPath := o.split()
opts.Path += "/file/" + urlEncode(enc.FromStandardName(bucket)) + "/" + urlEncode(enc.FromStandardPath(bucketPath)) opts.Path += "/file/" + urlEncode(o.fs.opt.Enc.FromStandardName(bucket)) + "/" + urlEncode(o.fs.opt.Enc.FromStandardPath(bucketPath))
} }
var resp *http.Response var resp *http.Response
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
@ -1808,7 +1814,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
Body: in, Body: in,
ExtraHeaders: map[string]string{ ExtraHeaders: map[string]string{
"Authorization": upload.AuthorizationToken, "Authorization": upload.AuthorizationToken,
"X-Bz-File-Name": urlEncode(enc.FromStandardPath(bucketPath)), "X-Bz-File-Name": urlEncode(o.fs.opt.Enc.FromStandardPath(bucketPath)),
"Content-Type": fs.MimeType(ctx, src), "Content-Type": fs.MimeType(ctx, src),
sha1Header: calculatedSha1, sha1Header: calculatedSha1,
timeHeader: timeString(modTime), timeHeader: timeString(modTime),

View File

@ -111,7 +111,7 @@ func (f *Fs) newLargeUpload(ctx context.Context, o *Object, in io.Reader, src fs
} }
var request = api.StartLargeFileRequest{ var request = api.StartLargeFileRequest{
BucketID: bucketID, BucketID: bucketID,
Name: enc.FromStandardPath(bucketPath), Name: f.opt.Enc.FromStandardPath(bucketPath),
ContentType: fs.MimeType(ctx, src), ContentType: fs.MimeType(ctx, src),
Info: map[string]string{ Info: map[string]string{
timeKey: timeString(modTime), timeKey: timeString(modTime),

View File

@ -25,6 +25,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/jwtutil" "github.com/rclone/rclone/lib/jwtutil"
"github.com/youmark/pkcs8" "github.com/youmark/pkcs8"
@ -48,8 +49,6 @@ import (
"golang.org/x/oauth2/jws" "golang.org/x/oauth2/jws"
) )
const enc = encodings.Box
const ( const (
rcloneClientID = "d0374ba6pgmaguie02ge15sv1mllndho" rcloneClientID = "d0374ba6pgmaguie02ge15sv1mllndho"
rcloneEncryptedClientSecret = "sYbJYm99WB8jzeaLPU0OPDMJKIkZvD2qOn3SyEMfiJr03RdtDt3xcZEIudRhbIDL" rcloneEncryptedClientSecret = "sYbJYm99WB8jzeaLPU0OPDMJKIkZvD2qOn3SyEMfiJr03RdtDt3xcZEIudRhbIDL"
@ -146,6 +145,11 @@ func init() {
Help: "Max number of times to try committing a multipart file.", Help: "Max number of times to try committing a multipart file.",
Default: 100, Default: 100,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Box,
}}, }},
}) })
} }
@ -222,6 +226,7 @@ func getDecryptedPrivateKey(boxConfig *api.ConfigJSON) (key *rsa.PrivateKey, err
type Options struct { type Options struct {
UploadCutoff fs.SizeSuffix `config:"upload_cutoff"` UploadCutoff fs.SizeSuffix `config:"upload_cutoff"`
CommitRetries int `config:"commit_retries"` CommitRetries int `config:"commit_retries"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote box // Fs represents a remote box
@ -488,7 +493,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string,
Parameters: fieldsValue(), Parameters: fieldsValue(),
} }
mkdir := api.CreateFolder{ mkdir := api.CreateFolder{
Name: enc.FromStandardName(leaf), Name: f.opt.Enc.FromStandardName(leaf),
Parent: api.Parent{ Parent: api.Parent{
ID: pathID, ID: pathID,
}, },
@ -554,7 +559,7 @@ OUTER:
if item.ItemStatus != api.ItemStatusActive { if item.ItemStatus != api.ItemStatusActive {
continue continue
} }
item.Name = enc.ToStandardName(item.Name) item.Name = f.opt.Enc.ToStandardName(item.Name)
if fn(item) { if fn(item) {
found = true found = true
break OUTER break OUTER
@ -791,7 +796,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
Parameters: fieldsValue(), Parameters: fieldsValue(),
} }
copyFile := api.CopyFile{ copyFile := api.CopyFile{
Name: enc.FromStandardName(leaf), Name: f.opt.Enc.FromStandardName(leaf),
Parent: api.Parent{ Parent: api.Parent{
ID: directoryID, ID: directoryID,
}, },
@ -830,7 +835,7 @@ func (f *Fs) move(ctx context.Context, endpoint, id, leaf, directoryID string) (
Parameters: fieldsValue(), Parameters: fieldsValue(),
} }
move := api.UpdateFileMove{ move := api.UpdateFileMove{
Name: enc.FromStandardName(leaf), Name: f.opt.Enc.FromStandardName(leaf),
Parent: api.Parent{ Parent: api.Parent{
ID: directoryID, ID: directoryID,
}, },
@ -1155,7 +1160,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
// This is recommended for less than 50 MB of content // This is recommended for less than 50 MB of content
func (o *Object) upload(ctx context.Context, in io.Reader, leaf, directoryID string, modTime time.Time) (err error) { func (o *Object) upload(ctx context.Context, in io.Reader, leaf, directoryID string, modTime time.Time) (err error) {
upload := api.UploadFile{ upload := api.UploadFile{
Name: enc.FromStandardName(leaf), Name: o.fs.opt.Enc.FromStandardName(leaf),
ContentModifiedAt: api.Time(modTime), ContentModifiedAt: api.Time(modTime),
ContentCreatedAt: api.Time(modTime), ContentCreatedAt: api.Time(modTime),
Parent: api.Parent{ Parent: api.Parent{

View File

@ -38,7 +38,7 @@ func (o *Object) createUploadSession(ctx context.Context, leaf, directoryID stri
} else { } else {
opts.Path = "/files/upload_sessions" opts.Path = "/files/upload_sessions"
request.FolderID = directoryID request.FolderID = directoryID
request.FileName = enc.FromStandardName(leaf) request.FileName = o.fs.opt.Enc.FromStandardName(leaf)
} }
var resp *http.Response var resp *http.Response
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {

View File

@ -39,6 +39,7 @@ import (
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
@ -49,8 +50,6 @@ import (
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
) )
const enc = encodings.Drive
// Constants // Constants
const ( const (
rcloneClientID = "202264815644.apps.googleusercontent.com" rcloneClientID = "202264815644.apps.googleusercontent.com"
@ -456,6 +455,11 @@ Google don't document so it may break in the future.
See: https://github.com/rclone/rclone/issues/3857 See: https://github.com/rclone/rclone/issues/3857
`, `,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Drive,
}}, }},
}) })
@ -506,6 +510,7 @@ type Options struct {
ServerSideAcrossConfigs bool `config:"server_side_across_configs"` ServerSideAcrossConfigs bool `config:"server_side_across_configs"`
DisableHTTP2 bool `config:"disable_http2"` DisableHTTP2 bool `config:"disable_http2"`
StopOnUploadLimit bool `config:"stop_on_upload_limit"` StopOnUploadLimit bool `config:"stop_on_upload_limit"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote drive server // Fs represents a remote drive server
@ -677,7 +682,7 @@ func (f *Fs) list(ctx context.Context, dirIDs []string, title string, directorie
} }
var stems []string var stems []string
if title != "" { if title != "" {
searchTitle := enc.FromStandardName(title) searchTitle := f.opt.Enc.FromStandardName(title)
// Escaping the backslash isn't documented but seems to work // Escaping the backslash isn't documented but seems to work
searchTitle = strings.Replace(searchTitle, `\`, `\\`, -1) searchTitle = strings.Replace(searchTitle, `\`, `\\`, -1)
searchTitle = strings.Replace(searchTitle, `'`, `\'`, -1) searchTitle = strings.Replace(searchTitle, `'`, `\'`, -1)
@ -751,7 +756,7 @@ OUTER:
return false, errors.Wrap(err, "couldn't list directory") return false, errors.Wrap(err, "couldn't list directory")
} }
for _, item := range files.Files { for _, item := range files.Files {
item.Name = enc.ToStandardName(item.Name) item.Name = f.opt.Enc.ToStandardName(item.Name)
// Check the case of items is correct since // Check the case of items is correct since
// the `=` operator is case insensitive. // the `=` operator is case insensitive.
if title != "" && title != item.Name { if title != "" && title != item.Name {
@ -1313,7 +1318,7 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut strin
// CreateDir makes a directory with pathID as parent and name leaf // CreateDir makes a directory with pathID as parent and name leaf
func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) { func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) {
leaf = enc.FromStandardName(leaf) leaf = f.opt.Enc.FromStandardName(leaf)
// fmt.Println("Making", path) // fmt.Println("Making", path)
// Define the metadata for the directory we are going to create. // Define the metadata for the directory we are going to create.
createInfo := &drive.File{ createInfo := &drive.File{
@ -1771,7 +1776,7 @@ func (f *Fs) createFileInfo(ctx context.Context, remote string, modTime time.Tim
return nil, err return nil, err
} }
leaf = enc.FromStandardName(leaf) leaf = f.opt.Enc.FromStandardName(leaf)
// Define the metadata for the file we are going to create. // Define the metadata for the file we are going to create.
createInfo := &drive.File{ createInfo := &drive.File{
Name: leaf, Name: leaf,
@ -2470,7 +2475,7 @@ func (f *Fs) changeNotifyRunner(ctx context.Context, notifyFunc func(string, fs.
// find the new path // find the new path
if change.File != nil { if change.File != nil {
change.File.Name = enc.ToStandardName(change.File.Name) change.File.Name = f.opt.Enc.ToStandardName(change.File.Name)
changeType := fs.EntryDirectory changeType := fs.EntryDirectory
if change.File.MimeType != driveFolderType { if change.File.MimeType != driveFolderType {
changeType = fs.EntryObject changeType = fs.EntryObject

View File

@ -48,14 +48,13 @@ import (
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.Dropbox
// Constants // Constants
const ( const (
rcloneClientID = "5jcck7diasz0rqy" rcloneClientID = "5jcck7diasz0rqy"
@ -147,6 +146,11 @@ memory. It can be set smaller if you are tight on memory.`, maxChunkSize),
Help: "Impersonate this user when using a business account.", Help: "Impersonate this user when using a business account.",
Default: "", Default: "",
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Dropbox,
}}, }},
}) })
} }
@ -155,6 +159,7 @@ memory. It can be set smaller if you are tight on memory.`, maxChunkSize),
type Options struct { type Options struct {
ChunkSize fs.SizeSuffix `config:"chunk_size"` ChunkSize fs.SizeSuffix `config:"chunk_size"`
Impersonate string `config:"impersonate"` Impersonate string `config:"impersonate"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote dropbox server // Fs represents a remote dropbox server
@ -381,7 +386,7 @@ func (f *Fs) setRoot(root string) {
func (f *Fs) getMetadata(objPath string) (entry files.IsMetadata, notFound bool, err error) { func (f *Fs) getMetadata(objPath string) (entry files.IsMetadata, notFound bool, err error) {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
entry, err = f.srv.GetMetadata(&files.GetMetadataArg{ entry, err = f.srv.GetMetadata(&files.GetMetadataArg{
Path: enc.FromStandardPath(objPath), Path: f.opt.Enc.FromStandardPath(objPath),
}) })
return shouldRetry(err) return shouldRetry(err)
}) })
@ -475,7 +480,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
for { for {
if !started { if !started {
arg := files.ListFolderArg{ arg := files.ListFolderArg{
Path: enc.FromStandardPath(root), Path: f.opt.Enc.FromStandardPath(root),
Recursive: false, Recursive: false,
} }
if root == "/" { if root == "/" {
@ -525,7 +530,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
// Only the last element is reliably cased in PathDisplay // Only the last element is reliably cased in PathDisplay
entryPath := metadata.PathDisplay entryPath := metadata.PathDisplay
leaf := enc.ToStandardName(path.Base(entryPath)) leaf := f.opt.Enc.ToStandardName(path.Base(entryPath))
remote := path.Join(dir, leaf) remote := path.Join(dir, leaf)
if folderInfo != nil { if folderInfo != nil {
d := fs.NewDir(remote, time.Now()) d := fs.NewDir(remote, time.Now())
@ -583,7 +588,7 @@ func (f *Fs) Mkdir(ctx context.Context, dir string) error {
// create it // create it
arg2 := files.CreateFolderArg{ arg2 := files.CreateFolderArg{
Path: enc.FromStandardPath(root), Path: f.opt.Enc.FromStandardPath(root),
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
_, err = f.srv.CreateFolderV2(&arg2) _, err = f.srv.CreateFolderV2(&arg2)
@ -609,7 +614,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
return errors.Wrap(err, "Rmdir") return errors.Wrap(err, "Rmdir")
} }
root = enc.FromStandardPath(root) root = f.opt.Enc.FromStandardPath(root)
// check directory empty // check directory empty
arg := files.ListFolderArg{ arg := files.ListFolderArg{
Path: root, Path: root,
@ -668,8 +673,8 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
// Copy // Copy
arg := files.RelocationArg{ arg := files.RelocationArg{
RelocationPath: files.RelocationPath{ RelocationPath: files.RelocationPath{
FromPath: enc.FromStandardPath(srcObj.remotePath()), FromPath: f.opt.Enc.FromStandardPath(srcObj.remotePath()),
ToPath: enc.FromStandardPath(dstObj.remotePath()), ToPath: f.opt.Enc.FromStandardPath(dstObj.remotePath()),
}, },
} }
var err error var err error
@ -704,7 +709,7 @@ func (f *Fs) Purge(ctx context.Context) (err error) {
// Let dropbox delete the filesystem tree // Let dropbox delete the filesystem tree
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
_, err = f.srv.DeleteV2(&files.DeleteArg{ _, err = f.srv.DeleteV2(&files.DeleteArg{
Path: enc.FromStandardPath(f.slashRoot), Path: f.opt.Enc.FromStandardPath(f.slashRoot),
}) })
return shouldRetry(err) return shouldRetry(err)
}) })
@ -736,8 +741,8 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
// Do the move // Do the move
arg := files.RelocationArg{ arg := files.RelocationArg{
RelocationPath: files.RelocationPath{ RelocationPath: files.RelocationPath{
FromPath: enc.FromStandardPath(srcObj.remotePath()), FromPath: f.opt.Enc.FromStandardPath(srcObj.remotePath()),
ToPath: enc.FromStandardPath(dstObj.remotePath()), ToPath: f.opt.Enc.FromStandardPath(dstObj.remotePath()),
}, },
} }
var err error var err error
@ -764,7 +769,7 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
// PublicLink adds a "readable by anyone with link" permission on the given file or folder. // PublicLink adds a "readable by anyone with link" permission on the given file or folder.
func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err error) { func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err error) {
absPath := enc.FromStandardPath(path.Join(f.slashRoot, remote)) absPath := f.opt.Enc.FromStandardPath(path.Join(f.slashRoot, remote))
fs.Debugf(f, "attempting to share '%s' (absolute path: %s)", remote, absPath) fs.Debugf(f, "attempting to share '%s' (absolute path: %s)", remote, absPath)
createArg := sharing.CreateSharedLinkWithSettingsArg{ createArg := sharing.CreateSharedLinkWithSettingsArg{
Path: absPath, Path: absPath,
@ -840,8 +845,8 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
// Do the move // Do the move
arg := files.RelocationArg{ arg := files.RelocationArg{
RelocationPath: files.RelocationPath{ RelocationPath: files.RelocationPath{
FromPath: enc.FromStandardPath(srcPath), FromPath: f.opt.Enc.FromStandardPath(srcPath),
ToPath: enc.FromStandardPath(dstPath), ToPath: f.opt.Enc.FromStandardPath(dstPath),
}, },
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
@ -999,7 +1004,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
fs.FixRangeOption(options, o.bytes) fs.FixRangeOption(options, o.bytes)
headers := fs.OpenOptionHeaders(options) headers := fs.OpenOptionHeaders(options)
arg := files.DownloadArg{ arg := files.DownloadArg{
Path: enc.FromStandardPath(o.remotePath()), Path: o.fs.opt.Enc.FromStandardPath(o.remotePath()),
ExtraHeaders: headers, ExtraHeaders: headers,
} }
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
@ -1130,7 +1135,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
if ignoredFiles.MatchString(remote) { if ignoredFiles.MatchString(remote) {
return fserrors.NoRetryError(errors.Errorf("file name %q is disallowed - not uploading", path.Base(remote))) return fserrors.NoRetryError(errors.Errorf("file name %q is disallowed - not uploading", path.Base(remote)))
} }
commitInfo := files.NewCommitInfo(enc.FromStandardPath(o.remotePath())) commitInfo := files.NewCommitInfo(o.fs.opt.Enc.FromStandardPath(o.remotePath()))
commitInfo.Mode.Tag = "overwrite" commitInfo.Mode.Tag = "overwrite"
// The Dropbox API only accepts timestamps in UTC with second precision. // The Dropbox API only accepts timestamps in UTC with second precision.
commitInfo.ClientModified = src.ModTime(ctx).UTC().Round(time.Second) commitInfo.ClientModified = src.ModTime(ctx).UTC().Round(time.Second)
@ -1156,7 +1161,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
func (o *Object) Remove(ctx context.Context) (err error) { func (o *Object) Remove(ctx context.Context) (err error) {
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
_, err = o.fs.srv.DeleteV2(&files.DeleteArg{ _, err = o.fs.srv.DeleteV2(&files.DeleteArg{
Path: enc.FromStandardPath(o.remotePath()), Path: o.fs.opt.Enc.FromStandardPath(o.remotePath()),
}) })
return shouldRetry(err) return shouldRetry(err)
}) })

View File

@ -109,7 +109,7 @@ func (f *Fs) listFiles(ctx context.Context, directoryID int) (filesList *FilesLi
} }
for i := range filesList.Items { for i := range filesList.Items {
item := &filesList.Items[i] item := &filesList.Items[i]
item.Filename = enc.ToStandardName(item.Filename) item.Filename = f.opt.Enc.ToStandardName(item.Filename)
} }
return filesList, nil return filesList, nil
@ -135,10 +135,10 @@ func (f *Fs) listFolders(ctx context.Context, directoryID int) (foldersList *Fol
if err != nil { if err != nil {
return nil, errors.Wrap(err, "couldn't list folders") return nil, errors.Wrap(err, "couldn't list folders")
} }
foldersList.Name = enc.ToStandardName(foldersList.Name) foldersList.Name = f.opt.Enc.ToStandardName(foldersList.Name)
for i := range foldersList.SubFolders { for i := range foldersList.SubFolders {
folder := &foldersList.SubFolders[i] folder := &foldersList.SubFolders[i]
folder.Name = enc.ToStandardName(folder.Name) folder.Name = f.opt.Enc.ToStandardName(folder.Name)
} }
// fs.Debugf(f, "Got FoldersList for id `%s`", directoryID) // fs.Debugf(f, "Got FoldersList for id `%s`", directoryID)
@ -213,7 +213,7 @@ func getRemote(dir, fileName string) string {
} }
func (f *Fs) makeFolder(ctx context.Context, leaf string, folderID int) (response *MakeFolderResponse, err error) { func (f *Fs) makeFolder(ctx context.Context, leaf string, folderID int) (response *MakeFolderResponse, err error) {
name := enc.FromStandardName(leaf) name := f.opt.Enc.FromStandardName(leaf)
// fs.Debugf(f, "Creating folder `%s` in id `%s`", name, directoryID) // fs.Debugf(f, "Creating folder `%s` in id `%s`", name, directoryID)
request := MakeFolderRequest{ request := MakeFolderRequest{
@ -323,7 +323,7 @@ func (f *Fs) getUploadNode(ctx context.Context) (response *GetUploadNodeResponse
func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, fileName, folderID, uploadID, node string) (response *http.Response, err error) { func (f *Fs) uploadFile(ctx context.Context, in io.Reader, size int64, fileName, folderID, uploadID, node string) (response *http.Response, err error) {
// fs.Debugf(f, "Uploading File `%s`", fileName) // fs.Debugf(f, "Uploading File `%s`", fileName)
fileName = enc.FromStandardName(fileName) fileName = f.opt.Enc.FromStandardName(fileName)
if len(uploadID) > 10 || !isAlphaNumeric(uploadID) { if len(uploadID) > 10 || !isAlphaNumeric(uploadID) {
return nil, errors.New("Invalid UploadID") return nil, errors.New("Invalid UploadID")

View File

@ -11,12 +11,14 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
) )
@ -29,8 +31,6 @@ const (
decayConstant = 2 // bigger for slower decay, exponential decayConstant = 2 // bigger for slower decay, exponential
) )
const enc = encodings.Fichier
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
Name: "fichier", Name: "fichier",
@ -38,18 +38,20 @@ func init() {
Config: func(name string, config configmap.Mapper) { Config: func(name string, config configmap.Mapper) {
}, },
NewFs: NewFs, NewFs: NewFs,
Options: []fs.Option{ Options: []fs.Option{{
{
Help: "Your API Key, get it from https://1fichier.com/console/params.pl", Help: "Your API Key, get it from https://1fichier.com/console/params.pl",
Name: "api_key", Name: "api_key",
}, }, {
{
Help: "If you want to download a shared folder, add this parameter", Help: "If you want to download a shared folder, add this parameter",
Name: "shared_folder", Name: "shared_folder",
Required: false, Required: false,
Advanced: true, Advanced: true,
}, }, {
}, Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Fichier,
}},
}) })
} }
@ -57,6 +59,7 @@ func init() {
type Options struct { type Options struct {
APIKey string `config:"api_key"` APIKey string `config:"api_key"`
SharedFolder string `config:"shared_folder"` SharedFolder string `config:"shared_folder"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs is the interface a cloud storage system must provide // Fs is the interface a cloud storage system must provide
@ -64,9 +67,9 @@ type Fs struct {
root string root string
name string name string
features *fs.Features features *fs.Features
opt Options
dirCache *dircache.DirCache dirCache *dircache.DirCache
baseClient *http.Client baseClient *http.Client
options *Options
pacer *fs.Pacer pacer *fs.Pacer
rest *rest.Client rest *rest.Client
} }
@ -162,7 +165,7 @@ func NewFs(name string, root string, config configmap.Mapper) (fs.Fs, error) {
f := &Fs{ f := &Fs{
name: name, name: name,
root: root, root: root,
options: opt, opt: *opt,
pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
baseClient: &http.Client{}, baseClient: &http.Client{},
} }
@ -176,7 +179,7 @@ func NewFs(name string, root string, config configmap.Mapper) (fs.Fs, error) {
f.rest = rest.NewClient(client).SetRoot(apiBaseURL) f.rest = rest.NewClient(client).SetRoot(apiBaseURL)
f.rest.SetHeader("Authorization", "Bearer "+f.options.APIKey) f.rest.SetHeader("Authorization", "Bearer "+f.opt.APIKey)
f.dirCache = dircache.New(root, rootID, f) f.dirCache = dircache.New(root, rootID, f)
@ -226,8 +229,8 @@ func NewFs(name string, root string, config configmap.Mapper) (fs.Fs, error) {
// This should return ErrDirNotFound if the directory isn't // This should return ErrDirNotFound if the directory isn't
// found. // found.
func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) { func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err error) {
if f.options.SharedFolder != "" { if f.opt.SharedFolder != "" {
return f.listSharedFiles(ctx, f.options.SharedFolder) return f.listSharedFiles(ctx, f.opt.SharedFolder)
} }
dirContent, err := f.listDir(ctx, dir) dirContent, err := f.listDir(ctx, dir)

View File

@ -14,25 +14,24 @@ import (
"github.com/jlaffaye/ftp" "github.com/jlaffaye/ftp"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
) )
const enc = encodings.FTP
// Register with Fs // Register with Fs
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
Name: "ftp", Name: "ftp",
Description: "FTP Connection", Description: "FTP Connection",
NewFs: NewFs, NewFs: NewFs,
Options: []fs.Option{ Options: []fs.Option{{
{
Name: "host", Name: "host",
Help: "FTP host to connect to", Help: "FTP host to connect to",
Required: true, Required: true,
@ -70,8 +69,12 @@ func init() {
Help: "Disable using EPSV even if server advertises support", Help: "Disable using EPSV even if server advertises support",
Default: false, Default: false,
Advanced: true, Advanced: true,
}, }, {
}, Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.FTP,
}},
}) })
} }
@ -85,6 +88,7 @@ type Options struct {
Concurrency int `config:"concurrency"` Concurrency int `config:"concurrency"`
SkipVerifyTLSCert bool `config:"no_check_certificate"` SkipVerifyTLSCert bool `config:"no_check_certificate"`
DisableEPSV bool `config:"disable_epsv"` DisableEPSV bool `config:"disable_epsv"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote FTP server // Fs represents a remote FTP server
@ -308,22 +312,22 @@ func translateErrorDir(err error) error {
} }
// entryToStandard converts an incoming ftp.Entry to Standard encoding // entryToStandard converts an incoming ftp.Entry to Standard encoding
func entryToStandard(entry *ftp.Entry) { func (f *Fs) entryToStandard(entry *ftp.Entry) {
// Skip . and .. as we don't want these encoded // Skip . and .. as we don't want these encoded
if entry.Name == "." || entry.Name == ".." { if entry.Name == "." || entry.Name == ".." {
return return
} }
entry.Name = enc.ToStandardName(entry.Name) entry.Name = f.opt.Enc.ToStandardName(entry.Name)
entry.Target = enc.ToStandardPath(entry.Target) entry.Target = f.opt.Enc.ToStandardPath(entry.Target)
} }
// dirFromStandardPath returns dir in encoded form. // dirFromStandardPath returns dir in encoded form.
func dirFromStandardPath(dir string) string { func (f *Fs) dirFromStandardPath(dir string) string {
// Skip . and .. as we don't want these encoded // Skip . and .. as we don't want these encoded
if dir == "." || dir == ".." { if dir == "." || dir == ".." {
return dir return dir
} }
return enc.FromStandardPath(dir) return f.opt.Enc.FromStandardPath(dir)
} }
// findItem finds a directory entry for the name in its parent directory // findItem finds a directory entry for the name in its parent directory
@ -345,13 +349,13 @@ func (f *Fs) findItem(remote string) (entry *ftp.Entry, err error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "findItem") return nil, errors.Wrap(err, "findItem")
} }
files, err := c.List(dirFromStandardPath(dir)) files, err := c.List(f.dirFromStandardPath(dir))
f.putFtpConnection(&c, err) f.putFtpConnection(&c, err)
if err != nil { if err != nil {
return nil, translateErrorFile(err) return nil, translateErrorFile(err)
} }
for _, file := range files { for _, file := range files {
entryToStandard(file) f.entryToStandard(file)
if file.Name == base { if file.Name == base {
return file, nil return file, nil
} }
@ -418,7 +422,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
resultchan := make(chan []*ftp.Entry, 1) resultchan := make(chan []*ftp.Entry, 1)
errchan := make(chan error, 1) errchan := make(chan error, 1)
go func() { go func() {
result, err := c.List(dirFromStandardPath(path.Join(f.root, dir))) result, err := c.List(f.dirFromStandardPath(path.Join(f.root, dir)))
f.putFtpConnection(&c, err) f.putFtpConnection(&c, err)
if err != nil { if err != nil {
errchan <- err errchan <- err
@ -455,7 +459,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
} }
for i := range files { for i := range files {
object := files[i] object := files[i]
entryToStandard(object) f.entryToStandard(object)
newremote := path.Join(dir, object.Name) newremote := path.Join(dir, object.Name)
switch object.Type { switch object.Type {
case ftp.EntryTypeFolder: case ftp.EntryTypeFolder:
@ -525,7 +529,7 @@ func (f *Fs) getInfo(remote string) (fi *FileInfo, err error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "getInfo") return nil, errors.Wrap(err, "getInfo")
} }
files, err := c.List(dirFromStandardPath(dir)) files, err := c.List(f.dirFromStandardPath(dir))
f.putFtpConnection(&c, err) f.putFtpConnection(&c, err)
if err != nil { if err != nil {
return nil, translateErrorFile(err) return nil, translateErrorFile(err)
@ -533,7 +537,7 @@ func (f *Fs) getInfo(remote string) (fi *FileInfo, err error) {
for i := range files { for i := range files {
file := files[i] file := files[i]
entryToStandard(file) f.entryToStandard(file)
if file.Name == base { if file.Name == base {
info := &FileInfo{ info := &FileInfo{
Name: remote, Name: remote,
@ -571,7 +575,7 @@ func (f *Fs) mkdir(abspath string) error {
if connErr != nil { if connErr != nil {
return errors.Wrap(connErr, "mkdir") return errors.Wrap(connErr, "mkdir")
} }
err = c.MakeDir(dirFromStandardPath(abspath)) err = c.MakeDir(f.dirFromStandardPath(abspath))
f.putFtpConnection(&c, err) f.putFtpConnection(&c, err)
switch errX := err.(type) { switch errX := err.(type) {
case *textproto.Error: case *textproto.Error:
@ -607,7 +611,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
if err != nil { if err != nil {
return errors.Wrap(translateErrorFile(err), "Rmdir") return errors.Wrap(translateErrorFile(err), "Rmdir")
} }
err = c.RemoveDir(dirFromStandardPath(path.Join(f.root, dir))) err = c.RemoveDir(f.dirFromStandardPath(path.Join(f.root, dir)))
f.putFtpConnection(&c, err) f.putFtpConnection(&c, err)
return translateErrorDir(err) return translateErrorDir(err)
} }
@ -628,8 +632,8 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
return nil, errors.Wrap(err, "Move") return nil, errors.Wrap(err, "Move")
} }
err = c.Rename( err = c.Rename(
enc.FromStandardPath(path.Join(srcObj.fs.root, srcObj.remote)), f.opt.Enc.FromStandardPath(path.Join(srcObj.fs.root, srcObj.remote)),
enc.FromStandardPath(path.Join(f.root, remote)), f.opt.Enc.FromStandardPath(path.Join(f.root, remote)),
) )
f.putFtpConnection(&c, err) f.putFtpConnection(&c, err)
if err != nil { if err != nil {
@ -682,8 +686,8 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
return errors.Wrap(err, "DirMove") return errors.Wrap(err, "DirMove")
} }
err = c.Rename( err = c.Rename(
dirFromStandardPath(srcPath), f.dirFromStandardPath(srcPath),
dirFromStandardPath(dstPath), f.dirFromStandardPath(dstPath),
) )
f.putFtpConnection(&c, err) f.putFtpConnection(&c, err)
if err != nil { if err != nil {
@ -809,7 +813,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (rc io.Read
if err != nil { if err != nil {
return nil, errors.Wrap(err, "open") return nil, errors.Wrap(err, "open")
} }
fd, err := c.RetrFrom(enc.FromStandardPath(path), uint64(offset)) fd, err := c.RetrFrom(o.fs.opt.Enc.FromStandardPath(path), uint64(offset))
if err != nil { if err != nil {
o.fs.putFtpConnection(&c, err) o.fs.putFtpConnection(&c, err)
return nil, errors.Wrap(err, "open") return nil, errors.Wrap(err, "open")
@ -844,7 +848,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
if err != nil { if err != nil {
return errors.Wrap(err, "Update") return errors.Wrap(err, "Update")
} }
err = c.Stor(enc.FromStandardPath(path), in) err = c.Stor(o.fs.opt.Enc.FromStandardPath(path), in)
if err != nil { if err != nil {
_ = c.Quit() // toss this connection to avoid sync errors _ = c.Quit() // toss this connection to avoid sync errors
remove() remove()
@ -874,7 +878,7 @@ func (o *Object) Remove(ctx context.Context) (err error) {
if err != nil { if err != nil {
return errors.Wrap(err, "Remove") return errors.Wrap(err, "Remove")
} }
err = c.Delete(enc.FromStandardPath(path)) err = c.Delete(o.fs.opt.Enc.FromStandardPath(path))
o.fs.putFtpConnection(&c, err) o.fs.putFtpConnection(&c, err)
} }
return err return err

View File

@ -38,6 +38,7 @@ import (
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/bucket" "github.com/rclone/rclone/lib/bucket"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -69,8 +70,6 @@ var (
} }
) )
const enc = encodings.GoogleCloudStorage
// Register with Fs // Register with Fs
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
@ -248,6 +247,11 @@ Docs: https://cloud.google.com/storage/docs/bucket-policy-only
Value: "DURABLE_REDUCED_AVAILABILITY", Value: "DURABLE_REDUCED_AVAILABILITY",
Help: "Durable reduced availability storage class", Help: "Durable reduced availability storage class",
}}, }},
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.GoogleCloudStorage,
}}, }},
}) })
} }
@ -262,6 +266,7 @@ type Options struct {
BucketPolicyOnly bool `config:"bucket_policy_only"` BucketPolicyOnly bool `config:"bucket_policy_only"`
Location string `config:"location"` Location string `config:"location"`
StorageClass string `config:"storage_class"` StorageClass string `config:"storage_class"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote storage server // Fs represents a remote storage server
@ -353,7 +358,7 @@ func parsePath(path string) (root string) {
// relative to f.root // relative to f.root
func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) { func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) {
bucketName, bucketPath = bucket.Split(path.Join(f.root, rootRelativePath)) bucketName, bucketPath = bucket.Split(path.Join(f.root, rootRelativePath))
return enc.FromStandardName(bucketName), enc.FromStandardPath(bucketPath) return f.opt.Enc.FromStandardName(bucketName), f.opt.Enc.FromStandardPath(bucketPath)
} }
// split returns bucket and bucketPath from the object // split returns bucket and bucketPath from the object
@ -442,7 +447,7 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
if f.rootBucket != "" && f.rootDirectory != "" { if f.rootBucket != "" && f.rootDirectory != "" {
// Check to see if the object exists // Check to see if the object exists
encodedDirectory := enc.FromStandardPath(f.rootDirectory) encodedDirectory := f.opt.Enc.FromStandardPath(f.rootDirectory)
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
_, err = f.svc.Objects.Get(f.rootBucket, encodedDirectory).Context(ctx).Do() _, err = f.svc.Objects.Get(f.rootBucket, encodedDirectory).Context(ctx).Do()
return shouldRetry(err) return shouldRetry(err)
@ -527,7 +532,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
if !strings.HasSuffix(remote, "/") { if !strings.HasSuffix(remote, "/") {
continue continue
} }
remote = enc.ToStandardPath(remote) remote = f.opt.Enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -543,7 +548,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
} }
} }
for _, object := range objects.Items { for _, object := range objects.Items {
remote := enc.ToStandardPath(object.Name) remote := f.opt.Enc.ToStandardPath(object.Name)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", object.Name) fs.Logf(f, "Odd name received %q", object.Name)
continue continue
@ -620,7 +625,7 @@ func (f *Fs) listBuckets(ctx context.Context) (entries fs.DirEntries, err error)
return nil, err return nil, err
} }
for _, bucket := range buckets.Items { for _, bucket := range buckets.Items {
d := fs.NewDir(enc.ToStandardName(bucket.Name), time.Time{}) d := fs.NewDir(f.opt.Enc.ToStandardName(bucket.Name), time.Time{})
entries = append(entries, d) entries = append(entries, d)
} }
if buckets.NextPageToken == "" { if buckets.NextPageToken == "" {

View File

@ -31,14 +31,13 @@ import (
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.JottaCloud
// Globals // Globals
const ( const (
minSleep = 10 * time.Millisecond minSleep = 10 * time.Millisecond
@ -157,6 +156,11 @@ func init() {
Help: "Files bigger than this can be resumed if the upload fail's.", Help: "Files bigger than this can be resumed if the upload fail's.",
Default: fs.SizeSuffix(10 * 1024 * 1024), Default: fs.SizeSuffix(10 * 1024 * 1024),
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.JottaCloud,
}}, }},
}) })
} }
@ -169,6 +173,7 @@ type Options struct {
HardDelete bool `config:"hard_delete"` HardDelete bool `config:"hard_delete"`
Unlink bool `config:"unlink"` Unlink bool `config:"unlink"`
UploadThreshold fs.SizeSuffix `config:"upload_resume_limit"` UploadThreshold fs.SizeSuffix `config:"upload_resume_limit"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote jottacloud // Fs represents a remote jottacloud
@ -446,7 +451,7 @@ func urlPathEscape(in string) string {
// filePathRaw returns an unescaped file path (f.root, file) // filePathRaw returns an unescaped file path (f.root, file)
func (f *Fs) filePathRaw(file string) string { func (f *Fs) filePathRaw(file string) string {
return path.Join(f.endpointURL, enc.FromStandardPath(path.Join(f.root, file))) return path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
} }
// filePath returns a escaped file path (f.root, file) // filePath returns a escaped file path (f.root, file)
@ -638,7 +643,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
if item.Deleted { if item.Deleted {
continue continue
} }
remote := path.Join(dir, enc.ToStandardName(item.Name)) remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
d := fs.NewDir(remote, time.Time(item.ModifiedAt)) d := fs.NewDir(remote, time.Time(item.ModifiedAt))
entries = append(entries, d) entries = append(entries, d)
} }
@ -648,7 +653,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
if item.Deleted || item.State != "COMPLETED" { if item.Deleted || item.State != "COMPLETED" {
continue continue
} }
remote := path.Join(dir, enc.ToStandardName(item.Name)) remote := path.Join(dir, f.opt.Enc.ToStandardName(item.Name))
o, err := f.newObjectWithInfo(ctx, remote, item) o, err := f.newObjectWithInfo(ctx, remote, item)
if err != nil { if err != nil {
continue continue
@ -673,7 +678,7 @@ func (f *Fs) listFileDir(ctx context.Context, remoteStartPath string, startFolde
if folder.Deleted { if folder.Deleted {
return nil return nil
} }
folderPath := enc.ToStandardPath(path.Join(folder.Path, folder.Name)) folderPath := f.opt.Enc.ToStandardPath(path.Join(folder.Path, folder.Name))
folderPathLength := len(folderPath) folderPathLength := len(folderPath)
var remoteDir string var remoteDir string
if folderPathLength > pathPrefixLength { if folderPathLength > pathPrefixLength {
@ -691,7 +696,7 @@ func (f *Fs) listFileDir(ctx context.Context, remoteStartPath string, startFolde
if file.Deleted || file.State != "COMPLETED" { if file.Deleted || file.State != "COMPLETED" {
continue continue
} }
remoteFile := path.Join(remoteDir, enc.ToStandardName(file.Name)) remoteFile := path.Join(remoteDir, f.opt.Enc.ToStandardName(file.Name))
o, err := f.newObjectWithInfo(ctx, remoteFile, file) o, err := f.newObjectWithInfo(ctx, remoteFile, file)
if err != nil { if err != nil {
return err return err
@ -862,7 +867,7 @@ func (f *Fs) copyOrMove(ctx context.Context, method, src, dest string) (info *ap
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set(method, "/"+path.Join(f.endpointURL, enc.FromStandardPath(path.Join(f.root, dest)))) opts.Parameters.Set(method, "/"+path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, dest))))
var resp *http.Response var resp *http.Response
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
@ -969,7 +974,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
return fs.ErrorDirExists return fs.ErrorDirExists
} }
_, err = f.copyOrMove(ctx, "mvDir", path.Join(f.endpointURL, enc.FromStandardPath(srcPath))+"/", dstRemote) _, err = f.copyOrMove(ctx, "mvDir", path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(srcPath))+"/", dstRemote)
if err != nil { if err != nil {
return errors.Wrap(err, "couldn't move directory") return errors.Wrap(err, "couldn't move directory")
@ -1260,7 +1265,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
Created: fileDate, Created: fileDate,
Modified: fileDate, Modified: fileDate,
Md5: md5String, Md5: md5String,
Path: path.Join(o.fs.opt.Mountpoint, enc.FromStandardPath(path.Join(o.fs.root, o.remote))), Path: path.Join(o.fs.opt.Mountpoint, o.fs.opt.Enc.FromStandardPath(path.Join(o.fs.root, o.remote))),
} }
// send it // send it

View File

@ -12,27 +12,26 @@ import (
"time" "time"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/encoder"
httpclient "github.com/koofr/go-httpclient" httpclient "github.com/koofr/go-httpclient"
koofrclient "github.com/koofr/go-koofrclient" koofrclient "github.com/koofr/go-koofrclient"
) )
const enc = encodings.Koofr
// Register Fs with rclone // Register Fs with rclone
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
Name: "koofr", Name: "koofr",
Description: "Koofr", Description: "Koofr",
NewFs: NewFs, NewFs: NewFs,
Options: []fs.Option{ Options: []fs.Option{{
{
Name: "endpoint", Name: "endpoint",
Help: "The Koofr API endpoint to use", Help: "The Koofr API endpoint to use",
Default: "https://app.koofr.net", Default: "https://app.koofr.net",
@ -59,8 +58,12 @@ func init() {
Help: "Your Koofr password for rclone (generate one at https://app.koofr.net/app/admin/preferences/password)", Help: "Your Koofr password for rclone (generate one at https://app.koofr.net/app/admin/preferences/password)",
IsPassword: true, IsPassword: true,
Required: true, Required: true,
}, }, {
}, Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Koofr,
}},
}) })
} }
@ -71,6 +74,7 @@ type Options struct {
User string `config:"user"` User string `config:"user"`
Password string `config:"password"` Password string `config:"password"`
SetMTime bool `config:"setmtime"` SetMTime bool `config:"setmtime"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// A Fs is a representation of a remote Koofr Fs // A Fs is a representation of a remote Koofr Fs
@ -246,7 +250,7 @@ func (f *Fs) Hashes() hash.Set {
// fullPath constructs a full, absolute path from a Fs root relative path, // fullPath constructs a full, absolute path from a Fs root relative path,
func (f *Fs) fullPath(part string) string { func (f *Fs) fullPath(part string) string {
return enc.FromStandardPath(path.Join("/", f.root, part)) return f.opt.Enc.FromStandardPath(path.Join("/", f.root, part))
} }
// NewFs constructs a new filesystem given a root path and configuration options // NewFs constructs a new filesystem given a root path and configuration options
@ -299,7 +303,7 @@ func NewFs(name, root string, m configmap.Mapper) (ff fs.Fs, err error) {
} }
return nil, errors.New("Failed to find mount " + opt.MountID) return nil, errors.New("Failed to find mount " + opt.MountID)
} }
rootFile, err := f.client.FilesInfo(f.mountID, enc.FromStandardPath("/"+f.root)) rootFile, err := f.client.FilesInfo(f.mountID, f.opt.Enc.FromStandardPath("/"+f.root))
if err == nil && rootFile.Type != "dir" { if err == nil && rootFile.Type != "dir" {
f.root = dir(f.root) f.root = dir(f.root)
err = fs.ErrorIsFile err = fs.ErrorIsFile
@ -317,7 +321,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
} }
entries = make([]fs.DirEntry, len(files)) entries = make([]fs.DirEntry, len(files))
for i, file := range files { for i, file := range files {
remote := path.Join(dir, enc.ToStandardName(file.Name)) remote := path.Join(dir, f.opt.Enc.ToStandardName(file.Name))
if file.Type == "dir" { if file.Type == "dir" {
entries[i] = fs.NewDir(remote, time.Unix(0, 0)) entries[i] = fs.NewDir(remote, time.Unix(0, 0))
} else { } else {

View File

@ -6,4 +6,4 @@ import (
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
) )
const enc = encodings.LocalMacOS const defaultEnc = encodings.LocalMacOS

View File

@ -6,4 +6,4 @@ import (
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
) )
const enc = encodings.LocalUnix const defaultEnc = encodings.LocalUnix

View File

@ -6,4 +6,4 @@ import (
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
) )
const enc = encodings.LocalWindows const defaultEnc = encodings.LocalWindows

View File

@ -20,10 +20,12 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/accounting" "github.com/rclone/rclone/fs/accounting"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/file" "github.com/rclone/rclone/lib/file"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
) )
@ -115,6 +117,11 @@ Windows/macOS and case sensitive for everything else. Use this flag
to override the default choice.`, to override the default choice.`,
Default: false, Default: false,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: defaultEnc,
}}, }},
} }
fs.Register(fsi) fs.Register(fsi)
@ -131,6 +138,7 @@ type Options struct {
OneFileSystem bool `config:"one_file_system"` OneFileSystem bool `config:"one_file_system"`
CaseSensitive bool `config:"case_sensitive"` CaseSensitive bool `config:"case_sensitive"`
CaseInsensitive bool `config:"case_insensitive"` CaseInsensitive bool `config:"case_insensitive"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a local filesystem rooted at root // Fs represents a local filesystem rooted at root
@ -189,7 +197,7 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
dev: devUnset, dev: devUnset,
lstat: os.Lstat, lstat: os.Lstat,
} }
f.root = cleanRootPath(root, f.opt.NoUNC) f.root = cleanRootPath(root, f.opt.NoUNC, f.opt.Enc)
f.features = (&fs.Features{ f.features = (&fs.Features{
CaseInsensitive: f.caseInsensitive(), CaseInsensitive: f.caseInsensitive(),
CanHaveEmptyDirectories: true, CanHaveEmptyDirectories: true,
@ -234,7 +242,7 @@ func (f *Fs) Name() string {
// Root of the remote (as passed into NewFs) // Root of the remote (as passed into NewFs)
func (f *Fs) Root() string { func (f *Fs) Root() string {
return enc.ToStandardPath(filepath.ToSlash(f.root)) return f.opt.Enc.ToStandardPath(filepath.ToSlash(f.root))
} }
// String converts this Fs to a string // String converts this Fs to a string
@ -443,7 +451,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
} }
func (f *Fs) cleanRemote(dir, filename string) (remote string) { func (f *Fs) cleanRemote(dir, filename string) (remote string) {
remote = path.Join(dir, enc.ToStandardName(filename)) remote = path.Join(dir, f.opt.Enc.ToStandardName(filename))
if !utf8.ValidString(filename) { if !utf8.ValidString(filename) {
f.warnedMu.Lock() f.warnedMu.Lock()
@ -457,7 +465,7 @@ func (f *Fs) cleanRemote(dir, filename string) (remote string) {
} }
func (f *Fs) localPath(name string) string { func (f *Fs) localPath(name string) string {
return filepath.Join(f.root, filepath.FromSlash(enc.FromStandardPath(name))) return filepath.Join(f.root, filepath.FromSlash(f.opt.Enc.FromStandardPath(name)))
} }
// Put the Object to the local filesystem // Put the Object to the local filesystem
@ -1092,7 +1100,7 @@ func (o *Object) Remove(ctx context.Context) error {
return remove(o.path) return remove(o.path)
} }
func cleanRootPath(s string, noUNC bool) string { func cleanRootPath(s string, noUNC bool, enc encoder.MultiEncoder) string {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
if !filepath.IsAbs(s) && !strings.HasPrefix(s, "\\") { if !filepath.IsAbs(s) && !strings.HasPrefix(s, "\\") {
s2, err := filepath.Abs(s) s2, err := filepath.Abs(s)

View File

@ -64,7 +64,7 @@ func TestCleanWindows(t *testing.T) {
t.Skipf("windows only") t.Skipf("windows only")
} }
for _, test := range testsWindows { for _, test := range testsWindows {
got := cleanRootPath(test[0], true) got := cleanRootPath(test[0], true, defaultEnc)
expect := test[1] expect := test[1]
if got != expect { if got != expect {
t.Fatalf("got %q, expected %q", got, expect) t.Fatalf("got %q, expected %q", got, expect)

View File

@ -24,6 +24,7 @@ import (
"github.com/rclone/rclone/backend/mailru/mrhash" "github.com/rclone/rclone/backend/mailru/mrhash"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
@ -34,6 +35,7 @@ import (
"github.com/rclone/rclone/fs/object" "github.com/rclone/rclone/fs/object"
"github.com/rclone/rclone/fs/operations" "github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
@ -42,8 +44,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.Mailru
// Global constants // Global constants
const ( const (
minSleepPacer = 10 * time.Millisecond minSleepPacer = 10 * time.Millisecond
@ -193,6 +193,11 @@ facilitate remote troubleshooting of backend issues. Strict meaning of
flags is not documented and not guaranteed to persist between releases. flags is not documented and not guaranteed to persist between releases.
Quirks will be removed when the backend grows stable. Quirks will be removed when the backend grows stable.
Supported quirks: atomicmkdir binlist gzip insecure retry400`, Supported quirks: atomicmkdir binlist gzip insecure retry400`,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Mailru,
}}, }},
}) })
} }
@ -208,6 +213,7 @@ type Options struct {
SpeedupMaxDisk fs.SizeSuffix `config:"speedup_max_disk"` SpeedupMaxDisk fs.SizeSuffix `config:"speedup_max_disk"`
SpeedupMaxMem fs.SizeSuffix `config:"speedup_max_memory"` SpeedupMaxMem fs.SizeSuffix `config:"speedup_max_memory"`
Quirks string `config:"quirks"` Quirks string `config:"quirks"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// retryErrorCodes is a slice of error codes that we will retry // retryErrorCodes is a slice of error codes that we will retry
@ -607,7 +613,7 @@ func (f *Fs) readItemMetaData(ctx context.Context, path string) (entry fs.DirEnt
Path: "/api/m1/file", Path: "/api/m1/file",
Parameters: url.Values{ Parameters: url.Values{
"access_token": {token}, "access_token": {token},
"home": {enc.FromStandardPath(path)}, "home": {f.opt.Enc.FromStandardPath(path)},
"offset": {"0"}, "offset": {"0"},
"limit": {strconv.Itoa(maxInt32)}, "limit": {strconv.Itoa(maxInt32)},
}, },
@ -642,7 +648,7 @@ func (f *Fs) readItemMetaData(ctx context.Context, path string) (entry fs.DirEnt
// =0 - for an empty directory // =0 - for an empty directory
// >0 - for a non-empty directory // >0 - for a non-empty directory
func (f *Fs) itemToDirEntry(ctx context.Context, item *api.ListItem) (entry fs.DirEntry, dirSize int, err error) { func (f *Fs) itemToDirEntry(ctx context.Context, item *api.ListItem) (entry fs.DirEntry, dirSize int, err error) {
remote, err := f.relPath(enc.ToStandardPath(item.Home)) remote, err := f.relPath(f.opt.Enc.ToStandardPath(item.Home))
if err != nil { if err != nil {
return nil, -1, err return nil, -1, err
} }
@ -708,7 +714,7 @@ func (f *Fs) listM1(ctx context.Context, dirPath string, offset int, limit int)
params.Set("limit", strconv.Itoa(limit)) params.Set("limit", strconv.Itoa(limit))
data := url.Values{} data := url.Values{}
data.Set("home", enc.FromStandardPath(dirPath)) data.Set("home", f.opt.Enc.FromStandardPath(dirPath))
opts := rest.Opts{ opts := rest.Opts{
Method: "POST", Method: "POST",
@ -756,7 +762,7 @@ func (f *Fs) listBin(ctx context.Context, dirPath string, depth int) (entries fs
req := api.NewBinWriter() req := api.NewBinWriter()
req.WritePu16(api.OperationFolderList) req.WritePu16(api.OperationFolderList)
req.WriteString(enc.FromStandardPath(dirPath)) req.WriteString(f.opt.Enc.FromStandardPath(dirPath))
req.WritePu32(int64(depth)) req.WritePu32(int64(depth))
req.WritePu32(int64(options)) req.WritePu32(int64(options))
req.WritePu32(0) req.WritePu32(0)
@ -892,7 +898,7 @@ func (t *treeState) NextRecord() (fs.DirEntry, error) {
if (head & 4096) != 0 { if (head & 4096) != 0 {
t.dunnoNodeID = r.ReadNBytes(api.DunnoNodeIDLength) t.dunnoNodeID = r.ReadNBytes(api.DunnoNodeIDLength)
} }
name := enc.FromStandardPath(string(r.ReadBytesByLength())) name := t.f.opt.Enc.FromStandardPath(string(r.ReadBytesByLength()))
t.dunno1 = int(r.ReadULong()) t.dunno1 = int(r.ReadULong())
t.dunno2 = 0 t.dunno2 = 0
t.dunno3 = 0 t.dunno3 = 0
@ -1031,7 +1037,7 @@ func (f *Fs) CreateDir(ctx context.Context, path string) error {
req := api.NewBinWriter() req := api.NewBinWriter()
req.WritePu16(api.OperationCreateFolder) req.WritePu16(api.OperationCreateFolder)
req.WritePu16(0) // revision req.WritePu16(0) // revision
req.WriteString(enc.FromStandardPath(path)) req.WriteString(f.opt.Enc.FromStandardPath(path))
req.WritePu32(0) req.WritePu32(0)
token, err := f.accessToken() token, err := f.accessToken()
@ -1186,7 +1192,7 @@ func (f *Fs) delete(ctx context.Context, path string, hardDelete bool) error {
return err return err
} }
data := url.Values{"home": {enc.FromStandardPath(path)}} data := url.Values{"home": {f.opt.Enc.FromStandardPath(path)}}
opts := rest.Opts{ opts := rest.Opts{
Method: "POST", Method: "POST",
Path: "/api/m1/file/remove", Path: "/api/m1/file/remove",
@ -1243,8 +1249,8 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
} }
data := url.Values{} data := url.Values{}
data.Set("home", enc.FromStandardPath(srcPath)) data.Set("home", f.opt.Enc.FromStandardPath(srcPath))
data.Set("folder", enc.FromStandardPath(parentDir(dstPath))) data.Set("folder", f.opt.Enc.FromStandardPath(parentDir(dstPath)))
data.Set("email", f.opt.Username) data.Set("email", f.opt.Username)
data.Set("x-email", f.opt.Username) data.Set("x-email", f.opt.Username)
@ -1282,7 +1288,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
return nil, fmt.Errorf("copy failed with code %d", response.Status) return nil, fmt.Errorf("copy failed with code %d", response.Status)
} }
tmpPath := enc.ToStandardPath(response.Body) tmpPath := f.opt.Enc.ToStandardPath(response.Body)
if tmpPath != dstPath { if tmpPath != dstPath {
// fs.Debugf(f, "rename temporary file %q -> %q\n", tmpPath, dstPath) // fs.Debugf(f, "rename temporary file %q -> %q\n", tmpPath, dstPath)
err = f.moveItemBin(ctx, tmpPath, dstPath, "rename temporary file") err = f.moveItemBin(ctx, tmpPath, dstPath, "rename temporary file")
@ -1357,9 +1363,9 @@ func (f *Fs) moveItemBin(ctx context.Context, srcPath, dstPath, opName string) e
req := api.NewBinWriter() req := api.NewBinWriter()
req.WritePu16(api.OperationRename) req.WritePu16(api.OperationRename)
req.WritePu32(0) // old revision req.WritePu32(0) // old revision
req.WriteString(enc.FromStandardPath(srcPath)) req.WriteString(f.opt.Enc.FromStandardPath(srcPath))
req.WritePu32(0) // new revision req.WritePu32(0) // new revision
req.WriteString(enc.FromStandardPath(dstPath)) req.WriteString(f.opt.Enc.FromStandardPath(dstPath))
req.WritePu32(0) // dunno req.WritePu32(0) // dunno
opts := rest.Opts{ opts := rest.Opts{
@ -1450,7 +1456,7 @@ func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err er
} }
data := url.Values{} data := url.Values{}
data.Set("home", enc.FromStandardPath(f.absPath(remote))) data.Set("home", f.opt.Enc.FromStandardPath(f.absPath(remote)))
data.Set("email", f.opt.Username) data.Set("email", f.opt.Username)
data.Set("x-email", f.opt.Username) data.Set("x-email", f.opt.Username)
@ -2015,7 +2021,7 @@ func (o *Object) addFileMetaData(ctx context.Context, overwrite bool) error {
req := api.NewBinWriter() req := api.NewBinWriter()
req.WritePu16(api.OperationAddFile) req.WritePu16(api.OperationAddFile)
req.WritePu16(0) // revision req.WritePu16(0) // revision
req.WriteString(enc.FromStandardPath(o.absPath())) req.WriteString(o.fs.opt.Enc.FromStandardPath(o.absPath()))
req.WritePu64(o.size) req.WritePu64(o.size)
req.WritePu64(o.modTime.Unix()) req.WritePu64(o.modTime.Unix())
req.WritePu32(0) req.WritePu32(0)
@ -2113,7 +2119,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
opts := rest.Opts{ opts := rest.Opts{
Method: "GET", Method: "GET",
Options: options, Options: options,
Path: url.PathEscape(strings.TrimLeft(enc.FromStandardPath(o.absPath()), "/")), Path: url.PathEscape(strings.TrimLeft(o.fs.opt.Enc.FromStandardPath(o.absPath()), "/")),
Parameters: url.Values{ Parameters: url.Values{
"client_id": {api.OAuthClientID}, "client_id": {api.OAuthClientID},
"token": {token}, "token": {token},

View File

@ -26,19 +26,19 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
mega "github.com/t3rm1n4l/go-mega" mega "github.com/t3rm1n4l/go-mega"
) )
const enc = encodings.Mega
const ( const (
minSleep = 10 * time.Millisecond minSleep = 10 * time.Millisecond
maxSleep = 2 * time.Second maxSleep = 2 * time.Second
@ -83,6 +83,11 @@ than permanently deleting them. If you specify this then rclone will
permanently delete objects instead.`, permanently delete objects instead.`,
Default: false, Default: false,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Mega,
}}, }},
}) })
} }
@ -93,6 +98,7 @@ type Options struct {
Pass string `config:"pass"` Pass string `config:"pass"`
Debug bool `config:"debug"` Debug bool `config:"debug"`
HardDelete bool `config:"hard_delete"` HardDelete bool `config:"hard_delete"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote mega // Fs represents a remote mega
@ -250,12 +256,12 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
// splitNodePath splits nodePath into / separated parts, returning nil if it // splitNodePath splits nodePath into / separated parts, returning nil if it
// should refer to the root. // should refer to the root.
// It also encodes the parts into backend specific encoding // It also encodes the parts into backend specific encoding
func splitNodePath(nodePath string) (parts []string) { func (f *Fs) splitNodePath(nodePath string) (parts []string) {
nodePath = path.Clean(nodePath) nodePath = path.Clean(nodePath)
if nodePath == "." || nodePath == "/" { if nodePath == "." || nodePath == "/" {
return nil return nil
} }
nodePath = enc.FromStandardPath(nodePath) nodePath = f.opt.Enc.FromStandardPath(nodePath)
return strings.Split(nodePath, "/") return strings.Split(nodePath, "/")
} }
@ -263,7 +269,7 @@ func splitNodePath(nodePath string) (parts []string) {
// //
// It returns mega.ENOENT if it wasn't found // It returns mega.ENOENT if it wasn't found
func (f *Fs) findNode(rootNode *mega.Node, nodePath string) (*mega.Node, error) { func (f *Fs) findNode(rootNode *mega.Node, nodePath string) (*mega.Node, error) {
parts := splitNodePath(nodePath) parts := f.splitNodePath(nodePath)
if parts == nil { if parts == nil {
return rootNode, nil return rootNode, nil
} }
@ -320,7 +326,7 @@ func (f *Fs) mkdir(rootNode *mega.Node, dir string) (node *mega.Node, err error)
f.mkdirMu.Lock() f.mkdirMu.Lock()
defer f.mkdirMu.Unlock() defer f.mkdirMu.Unlock()
parts := splitNodePath(dir) parts := f.splitNodePath(dir)
if parts == nil { if parts == nil {
return rootNode, nil return rootNode, nil
} }
@ -422,7 +428,7 @@ func (f *Fs) CleanUp(ctx context.Context) (err error) {
errors := 0 errors := 0
// similar to f.deleteNode(trash) but with HardDelete as true // similar to f.deleteNode(trash) but with HardDelete as true
for _, item := range items { for _, item := range items {
fs.Debugf(f, "Deleting trash %q", enc.ToStandardName(item.GetName())) fs.Debugf(f, "Deleting trash %q", f.opt.Enc.ToStandardName(item.GetName()))
deleteErr := f.pacer.Call(func() (bool, error) { deleteErr := f.pacer.Call(func() (bool, error) {
err := f.srv.Delete(item, true) err := f.srv.Delete(item, true)
return shouldRetry(err) return shouldRetry(err)
@ -504,7 +510,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
} }
var iErr error var iErr error
_, err = f.list(ctx, dirNode, func(info *mega.Node) bool { _, err = f.list(ctx, dirNode, func(info *mega.Node) bool {
remote := path.Join(dir, enc.ToStandardName(info.GetName())) remote := path.Join(dir, f.opt.Enc.ToStandardName(info.GetName()))
switch info.GetType() { switch info.GetType() {
case mega.FOLDER, mega.ROOT, mega.INBOX, mega.TRASH: case mega.FOLDER, mega.ROOT, mega.INBOX, mega.TRASH:
d := fs.NewDir(remote, info.GetTimeStamp()).SetID(info.GetHash()) d := fs.NewDir(remote, info.GetTimeStamp()).SetID(info.GetHash())
@ -726,7 +732,7 @@ func (f *Fs) move(dstRemote string, srcFs *Fs, srcRemote string, info *mega.Node
if srcLeaf != dstLeaf { if srcLeaf != dstLeaf {
//log.Printf("rename %q to %q", srcLeaf, dstLeaf) //log.Printf("rename %q to %q", srcLeaf, dstLeaf)
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
err = f.srv.Rename(info, enc.FromStandardName(dstLeaf)) err = f.srv.Rename(info, f.opt.Enc.FromStandardName(dstLeaf))
return shouldRetry(err) return shouldRetry(err)
}) })
if err != nil { if err != nil {
@ -875,13 +881,13 @@ func (f *Fs) MergeDirs(ctx context.Context, dirs []fs.Directory) error {
} }
// move them into place // move them into place
for _, info := range infos { for _, info := range infos {
fs.Infof(srcDir, "merging %q", enc.ToStandardName(info.GetName())) fs.Infof(srcDir, "merging %q", f.opt.Enc.ToStandardName(info.GetName()))
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
err = f.srv.Move(info, dstDirNode) err = f.srv.Move(info, dstDirNode)
return shouldRetry(err) return shouldRetry(err)
}) })
if err != nil { if err != nil {
return errors.Wrapf(err, "MergeDirs move failed on %q in %v", enc.ToStandardName(info.GetName()), srcDir) return errors.Wrapf(err, "MergeDirs move failed on %q in %v", f.opt.Enc.ToStandardName(info.GetName()), srcDir)
} }
} }
// rmdir (into trash) the now empty source directory // rmdir (into trash) the now empty source directory
@ -1124,7 +1130,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
var u *mega.Upload var u *mega.Upload
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
u, err = o.fs.srv.NewUpload(dirNode, enc.FromStandardName(leaf), size) u, err = o.fs.srv.NewUpload(dirNode, o.fs.opt.Enc.FromStandardName(leaf), size)
return shouldRetry(err) return shouldRetry(err)
}) })
if err != nil { if err != nil {

View File

@ -28,6 +28,7 @@ import (
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/atexit" "github.com/rclone/rclone/lib/atexit"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
@ -35,8 +36,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.OneDrive
const ( const (
rcloneClientID = "b15665d9-eda6-4092-8539-0eec376afd59" rcloneClientID = "b15665d9-eda6-4092-8539-0eec376afd59"
rcloneEncryptedClientSecret = "_JUdzh3LnKNqSPcf4Wu5fgMFIQOI8glZu_akYgR8yf6egowNBg-R" rcloneEncryptedClientSecret = "_JUdzh3LnKNqSPcf4Wu5fgMFIQOI8glZu_akYgR8yf6egowNBg-R"
@ -252,6 +251,11 @@ delete OneNote files or otherwise want them to show up in directory
listing, set this option.`, listing, set this option.`,
Default: false, Default: false,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.OneDrive,
}}, }},
}) })
} }
@ -262,6 +266,7 @@ type Options struct {
DriveID string `config:"drive_id"` DriveID string `config:"drive_id"`
DriveType string `config:"drive_type"` DriveType string `config:"drive_type"`
ExposeOneNoteFiles bool `config:"expose_onenote_files"` ExposeOneNoteFiles bool `config:"expose_onenote_files"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote one drive // Fs represents a remote one drive
@ -355,7 +360,7 @@ func shouldRetry(resp *http.Response, err error) (bool, error) {
// If `relPath` == '', do not append the slash (See #3664) // If `relPath` == '', do not append the slash (See #3664)
func (f *Fs) readMetaDataForPathRelativeToID(ctx context.Context, normalizedID string, relPath string) (info *api.Item, resp *http.Response, err error) { func (f *Fs) readMetaDataForPathRelativeToID(ctx context.Context, normalizedID string, relPath string) (info *api.Item, resp *http.Response, err error) {
if relPath != "" { if relPath != "" {
relPath = "/" + withTrailingColon(rest.URLPathEscape(enc.FromStandardPath(relPath))) relPath = "/" + withTrailingColon(rest.URLPathEscape(f.opt.Enc.FromStandardPath(relPath)))
} }
opts := newOptsCall(normalizedID, "GET", ":"+relPath) opts := newOptsCall(normalizedID, "GET", ":"+relPath)
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
@ -380,7 +385,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string) (info *api.It
} else { } else {
opts = rest.Opts{ opts = rest.Opts{
Method: "GET", Method: "GET",
Path: "/root:/" + rest.URLPathEscape(enc.FromStandardPath(path)), Path: "/root:/" + rest.URLPathEscape(f.opt.Enc.FromStandardPath(path)),
} }
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
@ -628,7 +633,7 @@ func (f *Fs) CreateDir(ctx context.Context, dirID, leaf string) (newID string, e
var info *api.Item var info *api.Item
opts := newOptsCall(dirID, "POST", "/children") opts := newOptsCall(dirID, "POST", "/children")
mkdir := api.CreateItemRequest{ mkdir := api.CreateItemRequest{
Name: enc.FromStandardName(leaf), Name: f.opt.Enc.FromStandardName(leaf),
ConflictBehavior: "fail", ConflictBehavior: "fail",
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
@ -688,7 +693,7 @@ OUTER:
if item.Deleted != nil { if item.Deleted != nil {
continue continue
} }
item.Name = enc.ToStandardName(item.GetName()) item.Name = f.opt.Enc.ToStandardName(item.GetName())
if fn(item) { if fn(item) {
found = true found = true
break OUTER break OUTER
@ -944,7 +949,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
id, dstDriveID, _ := parseNormalizedID(directoryID) id, dstDriveID, _ := parseNormalizedID(directoryID)
replacedLeaf := enc.FromStandardName(leaf) replacedLeaf := f.opt.Enc.FromStandardName(leaf)
copyReq := api.CopyItemRequest{ copyReq := api.CopyItemRequest{
Name: &replacedLeaf, Name: &replacedLeaf,
ParentReference: api.ItemReference{ ParentReference: api.ItemReference{
@ -1028,7 +1033,7 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
opts := newOptsCall(srcObj.id, "PATCH", "") opts := newOptsCall(srcObj.id, "PATCH", "")
move := api.MoveItemRequest{ move := api.MoveItemRequest{
Name: enc.FromStandardName(leaf), Name: f.opt.Enc.FromStandardName(leaf),
ParentReference: &api.ItemReference{ ParentReference: &api.ItemReference{
DriveID: dstDriveID, DriveID: dstDriveID,
ID: id, ID: id,
@ -1143,7 +1148,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
// Do the move // Do the move
opts := newOptsCall(srcID, "PATCH", "") opts := newOptsCall(srcID, "PATCH", "")
move := api.MoveItemRequest{ move := api.MoveItemRequest{
Name: enc.FromStandardName(leaf), Name: f.opt.Enc.FromStandardName(leaf),
ParentReference: &api.ItemReference{ ParentReference: &api.ItemReference{
DriveID: dstDriveID, DriveID: dstDriveID,
ID: parsedDstDirID, ID: parsedDstDirID,
@ -1265,7 +1270,7 @@ func (o *Object) rootPath() string {
// srvPath returns a path for use in server given a remote // srvPath returns a path for use in server given a remote
func (f *Fs) srvPath(remote string) string { func (f *Fs) srvPath(remote string) string {
return enc.FromStandardPath(f.rootSlash() + remote) return f.opt.Enc.FromStandardPath(f.rootSlash() + remote)
} }
// srvPath returns a path for use in server // srvPath returns a path for use in server
@ -1377,7 +1382,7 @@ func (o *Object) setModTime(ctx context.Context, modTime time.Time) (*api.Item,
opts = rest.Opts{ opts = rest.Opts{
Method: "PATCH", Method: "PATCH",
RootURL: rootURL, RootURL: rootURL,
Path: "/" + drive + "/items/" + trueDirID + ":/" + withTrailingColon(rest.URLPathEscape(enc.FromStandardName(leaf))), Path: "/" + drive + "/items/" + trueDirID + ":/" + withTrailingColon(rest.URLPathEscape(o.fs.opt.Enc.FromStandardName(leaf))),
} }
} else { } else {
opts = rest.Opts{ opts = rest.Opts{
@ -1452,7 +1457,7 @@ func (o *Object) createUploadSession(ctx context.Context, modTime time.Time) (re
Method: "POST", Method: "POST",
RootURL: rootURL, RootURL: rootURL,
Path: fmt.Sprintf("/%s/items/%s:/%s:/createUploadSession", Path: fmt.Sprintf("/%s/items/%s:/%s:/createUploadSession",
drive, id, rest.URLPathEscape(enc.FromStandardName(leaf))), drive, id, rest.URLPathEscape(o.fs.opt.Enc.FromStandardName(leaf))),
} }
} else { } else {
opts = rest.Opts{ opts = rest.Opts{
@ -1604,7 +1609,7 @@ func (o *Object) uploadSinglepart(ctx context.Context, in io.Reader, size int64,
opts = rest.Opts{ opts = rest.Opts{
Method: "PUT", Method: "PUT",
RootURL: rootURL, RootURL: rootURL,
Path: "/" + drive + "/items/" + trueDirID + ":/" + rest.URLPathEscape(enc.FromStandardName(leaf)) + ":/content", Path: "/" + drive + "/items/" + trueDirID + ":/" + rest.URLPathEscape(o.fs.opt.Enc.FromStandardName(leaf)) + ":/content",
ContentLength: &size, ContentLength: &size,
Body: in, Body: in,
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
@ -21,13 +22,12 @@ import (
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
) )
const enc = encodings.OpenDrive
const ( const (
defaultEndpoint = "https://dev.opendrive.com/api/v1" defaultEndpoint = "https://dev.opendrive.com/api/v1"
minSleep = 10 * time.Millisecond minSleep = 10 * time.Millisecond
@ -50,6 +50,11 @@ func init() {
Help: "Password.", Help: "Password.",
IsPassword: true, IsPassword: true,
Required: true, Required: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.OpenDrive,
}}, }},
}) })
} }
@ -58,6 +63,7 @@ func init() {
type Options struct { type Options struct {
UserName string `config:"username"` UserName string `config:"username"`
Password string `config:"password"` Password string `config:"password"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote server // Fs represents a remote server
@ -588,7 +594,7 @@ func (f *Fs) createObject(ctx context.Context, remote string, modTime time.Time,
fs: f, fs: f,
remote: remote, remote: remote,
} }
return o, enc.FromStandardName(leaf), directoryID, nil return o, f.opt.Enc.FromStandardName(leaf), directoryID, nil
} }
// readMetaDataForPath reads the metadata from the path // readMetaDataForPath reads the metadata from the path
@ -690,7 +696,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string,
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
createDirData := createFolder{ createDirData := createFolder{
SessionID: f.session.SessionID, SessionID: f.session.SessionID,
FolderName: enc.FromStandardName(leaf), FolderName: f.opt.Enc.FromStandardName(leaf),
FolderSubParent: pathID, FolderSubParent: pathID,
FolderIsPublic: 0, FolderIsPublic: 0,
FolderPublicUpl: 0, FolderPublicUpl: 0,
@ -736,7 +742,7 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut strin
return "", false, errors.Wrap(err, "failed to get folder list") return "", false, errors.Wrap(err, "failed to get folder list")
} }
leaf = enc.FromStandardName(leaf) leaf = f.opt.Enc.FromStandardName(leaf)
for _, folder := range folderList.Folders { for _, folder := range folderList.Folders {
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID) // fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
@ -784,7 +790,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
} }
for _, folder := range folderList.Folders { for _, folder := range folderList.Folders {
folder.Name = enc.ToStandardName(folder.Name) folder.Name = f.opt.Enc.ToStandardName(folder.Name)
// fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID) // fs.Debugf(nil, "Folder: %s (%s)", folder.Name, folder.FolderID)
remote := path.Join(dir, folder.Name) remote := path.Join(dir, folder.Name)
// cache the directory ID for later lookups // cache the directory ID for later lookups
@ -795,7 +801,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
} }
for _, file := range folderList.Files { for _, file := range folderList.Files {
file.Name = enc.ToStandardName(file.Name) file.Name = f.opt.Enc.ToStandardName(file.Name)
// fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID) // fs.Debugf(nil, "File: %s (%s)", file.Name, file.FileID)
remote := path.Join(dir, file.Name) remote := path.Join(dir, file.Name)
o, err := f.newObjectWithInfo(ctx, remote, &file) o, err := f.newObjectWithInfo(ctx, remote, &file)
@ -1050,7 +1056,7 @@ func (o *Object) readMetaData(ctx context.Context) (err error) {
opts := rest.Opts{ opts := rest.Opts{
Method: "GET", Method: "GET",
Path: fmt.Sprintf("/folder/itembyname.json/%s/%s?name=%s", Path: fmt.Sprintf("/folder/itembyname.json/%s/%s?name=%s",
o.fs.session.SessionID, directoryID, url.QueryEscape(enc.FromStandardName(leaf))), o.fs.session.SessionID, directoryID, url.QueryEscape(o.fs.opt.Enc.FromStandardName(leaf))),
} }
resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &folderList) resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &folderList)
return o.fs.shouldRetry(resp, err) return o.fs.shouldRetry(resp, err)

View File

@ -30,14 +30,13 @@ import (
"github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.Pcloud
const ( const (
rcloneClientID = "DnONSzyJXpm" rcloneClientID = "DnONSzyJXpm"
rcloneEncryptedClientSecret = "ej1OIF39VOQQ0PXaSdK9ztkLw3tdLNscW2157TKNQdQKkICR4uU7aFg4eFM" rcloneEncryptedClientSecret = "ej1OIF39VOQQ0PXaSdK9ztkLw3tdLNscW2157TKNQdQKkICR4uU7aFg4eFM"
@ -81,12 +80,18 @@ func init() {
}, { }, {
Name: config.ConfigClientSecret, Name: config.ConfigClientSecret,
Help: "Pcloud App Client Secret\nLeave blank normally.", Help: "Pcloud App Client Secret\nLeave blank normally.",
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Pcloud,
}}, }},
}) })
} }
// Options defines the configuration for this backend // Options defines the configuration for this backend
type Options struct { type Options struct {
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote pcloud // Fs represents a remote pcloud
@ -342,7 +347,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string,
Path: "/createfolder", Path: "/createfolder",
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("name", enc.FromStandardName(leaf)) opts.Parameters.Set("name", f.opt.Enc.FromStandardName(leaf))
opts.Parameters.Set("folderid", dirIDtoNumber(pathID)) opts.Parameters.Set("folderid", dirIDtoNumber(pathID))
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
resp, err = f.srv.CallJSON(ctx, &opts, nil, &result) resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
@ -418,7 +423,7 @@ func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, fi
continue continue
} }
} }
item.Name = enc.ToStandardName(item.Name) item.Name = f.opt.Enc.ToStandardName(item.Name)
if fn(item) { if fn(item) {
found = true found = true
break break
@ -610,7 +615,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("fileid", fileIDtoNumber(srcObj.id)) opts.Parameters.Set("fileid", fileIDtoNumber(srcObj.id))
opts.Parameters.Set("toname", enc.FromStandardName(leaf)) opts.Parameters.Set("toname", f.opt.Enc.FromStandardName(leaf))
opts.Parameters.Set("tofolderid", dirIDtoNumber(directoryID)) opts.Parameters.Set("tofolderid", dirIDtoNumber(directoryID))
opts.Parameters.Set("mtime", fmt.Sprintf("%d", srcObj.modTime.Unix())) opts.Parameters.Set("mtime", fmt.Sprintf("%d", srcObj.modTime.Unix()))
var resp *http.Response var resp *http.Response
@ -689,7 +694,7 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (fs.Object,
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("fileid", fileIDtoNumber(srcObj.id)) opts.Parameters.Set("fileid", fileIDtoNumber(srcObj.id))
opts.Parameters.Set("toname", enc.FromStandardName(leaf)) opts.Parameters.Set("toname", f.opt.Enc.FromStandardName(leaf))
opts.Parameters.Set("tofolderid", dirIDtoNumber(directoryID)) opts.Parameters.Set("tofolderid", dirIDtoNumber(directoryID))
var resp *http.Response var resp *http.Response
var result api.ItemResult var result api.ItemResult
@ -786,7 +791,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("folderid", dirIDtoNumber(srcID)) opts.Parameters.Set("folderid", dirIDtoNumber(srcID))
opts.Parameters.Set("toname", enc.FromStandardName(leaf)) opts.Parameters.Set("toname", f.opt.Enc.FromStandardName(leaf))
opts.Parameters.Set("tofolderid", dirIDtoNumber(directoryID)) opts.Parameters.Set("tofolderid", dirIDtoNumber(directoryID))
var resp *http.Response var resp *http.Response
var result api.ItemResult var result api.ItemResult
@ -1066,7 +1071,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
Parameters: url.Values{}, Parameters: url.Values{},
TransferEncoding: []string{"identity"}, // pcloud doesn't like chunked encoding TransferEncoding: []string{"identity"}, // pcloud doesn't like chunked encoding
} }
leaf = enc.FromStandardName(leaf) leaf = o.fs.opt.Enc.FromStandardName(leaf)
opts.Parameters.Set("filename", leaf) opts.Parameters.Set("filename", leaf)
opts.Parameters.Set("folderid", dirIDtoNumber(directoryID)) opts.Parameters.Set("folderid", dirIDtoNumber(directoryID))
opts.Parameters.Set("nopartial", "1") opts.Parameters.Set("nopartial", "1")

View File

@ -31,6 +31,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/backend/premiumizeme/api" "github.com/rclone/rclone/backend/premiumizeme/api"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
@ -39,6 +40,7 @@ import (
"github.com/rclone/rclone/fs/fshttp" "github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/random" "github.com/rclone/rclone/lib/random"
@ -46,8 +48,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.PremiumizeMe
const ( const (
rcloneClientID = "658922194" rcloneClientID = "658922194"
rcloneEncryptedClientSecret = "B5YIvQoRIhcpAYs8HYeyjb9gK-ftmZEbqdh_gNfc4RgO9Q" rcloneEncryptedClientSecret = "B5YIvQoRIhcpAYs8HYeyjb9gK-ftmZEbqdh_gNfc4RgO9Q"
@ -93,6 +93,11 @@ This is not normally used - use oauth instead.
`, `,
Hide: fs.OptionHideBoth, Hide: fs.OptionHideBoth,
Default: "", Default: "",
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.PremiumizeMe,
}}, }},
}) })
} }
@ -100,6 +105,7 @@ This is not normally used - use oauth instead.
// Options defines the configuration for this backend // Options defines the configuration for this backend
type Options struct { type Options struct {
APIKey string `config:"api_key"` APIKey string `config:"api_key"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote cloud storage system // Fs represents a remote cloud storage system
@ -364,7 +370,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string,
Path: "/folder/create", Path: "/folder/create",
Parameters: f.baseParams(), Parameters: f.baseParams(),
MultipartParams: url.Values{ MultipartParams: url.Values{
"name": {enc.FromStandardName(leaf)}, "name": {f.opt.Enc.FromStandardName(leaf)},
"parent_id": {pathID}, "parent_id": {pathID},
}, },
} }
@ -429,7 +435,7 @@ func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, fi
fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type) fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type)
continue continue
} }
item.Name = enc.ToStandardName(item.Name) item.Name = f.opt.Enc.ToStandardName(item.Name)
if fn(item) { if fn(item) {
found = true found = true
break break
@ -637,8 +643,8 @@ func (f *Fs) Purge(ctx context.Context) error {
// between directories and a separate one to rename them. We try to // between directories and a separate one to rename them. We try to
// call the minimum number of API calls. // call the minimum number of API calls.
func (f *Fs) move(ctx context.Context, isFile bool, id, oldLeaf, newLeaf, oldDirectoryID, newDirectoryID string) (err error) { func (f *Fs) move(ctx context.Context, isFile bool, id, oldLeaf, newLeaf, oldDirectoryID, newDirectoryID string) (err error) {
newLeaf = enc.FromStandardName(newLeaf) newLeaf = f.opt.Enc.FromStandardName(newLeaf)
oldLeaf = enc.FromStandardName(oldLeaf) oldLeaf = f.opt.Enc.FromStandardName(oldLeaf)
doRenameLeaf := oldLeaf != newLeaf doRenameLeaf := oldLeaf != newLeaf
doMove := oldDirectoryID != newDirectoryID doMove := oldDirectoryID != newDirectoryID
@ -891,7 +897,7 @@ func (o *Object) Remote() string {
// srvPath returns a path for use in server // srvPath returns a path for use in server
func (o *Object) srvPath() string { func (o *Object) srvPath() string {
return enc.FromStandardPath(o.fs.rootSlash() + o.remote) return o.fs.opt.Enc.FromStandardPath(o.fs.rootSlash() + o.remote)
} }
// Hash returns the SHA-1 of an object returning a lowercase hex string // Hash returns the SHA-1 of an object returning a lowercase hex string
@ -1006,7 +1012,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
if err != nil { if err != nil {
return err return err
} }
leaf = enc.FromStandardName(leaf) leaf = o.fs.opt.Enc.FromStandardName(leaf)
var resp *http.Response var resp *http.Response
var info api.FolderUploadinfoResponse var info api.FolderUploadinfoResponse

View File

@ -17,6 +17,7 @@ import (
"github.com/putdotio/go-putio/putio" "github.com/putdotio/go-putio/putio"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
@ -29,6 +30,7 @@ type Fs struct {
name string // name of this remote name string // name of this remote
root string // the path we are working on root string // the path we are working on
features *fs.Features // optional features features *fs.Features // optional features
opt Options // options for this Fs
client *putio.Client // client for making API calls to Put.io client *putio.Client // client for making API calls to Put.io
pacer *fs.Pacer // To pace the API calls pacer *fs.Pacer // To pace the API calls
dirCache *dircache.DirCache // Map of directory path to directory id dirCache *dircache.DirCache // Map of directory path to directory id
@ -60,6 +62,12 @@ func (f *Fs) Features() *fs.Features {
// NewFs constructs an Fs from the path, container:path // NewFs constructs an Fs from the path, container:path
func NewFs(name, root string, m configmap.Mapper) (f fs.Fs, err error) { func NewFs(name, root string, m configmap.Mapper) (f fs.Fs, err error) {
// defer log.Trace(name, "root=%v", root)("f=%+v, err=%v", &f, &err) // defer log.Trace(name, "root=%v", root)("f=%+v, err=%v", &f, &err)
// Parse config into Options struct
opt := new(Options)
err = configstruct.Set(m, opt)
if err != nil {
return nil, err
}
oAuthClient, _, err := oauthutil.NewClient(name, m, putioConfig) oAuthClient, _, err := oauthutil.NewClient(name, m, putioConfig)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to configure putio") return nil, errors.Wrap(err, "failed to configure putio")
@ -67,6 +75,7 @@ func NewFs(name, root string, m configmap.Mapper) (f fs.Fs, err error) {
p := &Fs{ p := &Fs{
name: name, name: name,
root: root, root: root,
opt: *opt,
pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))), pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
client: putio.NewClient(oAuthClient), client: putio.NewClient(oAuthClient),
oAuthClient: oAuthClient, oAuthClient: oAuthClient,
@ -127,7 +136,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string,
var entry putio.File var entry putio.File
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
// fs.Debugf(f, "creating folder. part: %s, parentID: %d", leaf, parentID) // fs.Debugf(f, "creating folder. part: %s, parentID: %d", leaf, parentID)
entry, err = f.client.Files.CreateFolder(ctx, enc.FromStandardName(leaf), parentID) entry, err = f.client.Files.CreateFolder(ctx, f.opt.Enc.FromStandardName(leaf), parentID)
return shouldRetry(err) return shouldRetry(err)
}) })
return itoa(entry.ID), err return itoa(entry.ID), err
@ -154,7 +163,7 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut strin
return return
} }
for _, child := range children { for _, child := range children {
if enc.ToStandardName(child.Name) == leaf { if f.opt.Enc.ToStandardName(child.Name) == leaf {
found = true found = true
pathIDOut = itoa(child.ID) pathIDOut = itoa(child.ID)
if !child.IsDir() { if !child.IsDir() {
@ -196,7 +205,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
return return
} }
for _, child := range children { for _, child := range children {
remote := path.Join(dir, enc.ToStandardName(child.Name)) remote := path.Join(dir, f.opt.Enc.ToStandardName(child.Name))
// fs.Debugf(f, "child: %s", remote) // fs.Debugf(f, "child: %s", remote)
if child.IsDir() { if child.IsDir() {
f.dirCache.Put(remote, itoa(child.ID)) f.dirCache.Put(remote, itoa(child.ID))
@ -274,7 +283,7 @@ func (f *Fs) createUpload(ctx context.Context, name string, size int64, parentID
req = req.WithContext(ctx) // go1.13 can use NewRequestWithContext req = req.WithContext(ctx) // go1.13 can use NewRequestWithContext
req.Header.Set("tus-resumable", "1.0.0") req.Header.Set("tus-resumable", "1.0.0")
req.Header.Set("upload-length", strconv.FormatInt(size, 10)) req.Header.Set("upload-length", strconv.FormatInt(size, 10))
b64name := base64.StdEncoding.EncodeToString([]byte(enc.FromStandardName(name))) b64name := base64.StdEncoding.EncodeToString([]byte(f.opt.Enc.FromStandardName(name)))
b64true := base64.StdEncoding.EncodeToString([]byte("true")) b64true := base64.StdEncoding.EncodeToString([]byte("true"))
b64parentID := base64.StdEncoding.EncodeToString([]byte(parentID)) b64parentID := base64.StdEncoding.EncodeToString([]byte(parentID))
b64modifiedAt := base64.StdEncoding.EncodeToString([]byte(modTime.Format(time.RFC3339))) b64modifiedAt := base64.StdEncoding.EncodeToString([]byte(modTime.Format(time.RFC3339)))
@ -546,7 +555,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (o fs.Objec
params := url.Values{} params := url.Values{}
params.Set("file_id", strconv.FormatInt(srcObj.file.ID, 10)) params.Set("file_id", strconv.FormatInt(srcObj.file.ID, 10))
params.Set("parent_id", directoryID) params.Set("parent_id", directoryID)
params.Set("name", enc.FromStandardName(leaf)) params.Set("name", f.opt.Enc.FromStandardName(leaf))
req, err := f.client.NewRequest(ctx, "POST", "/v2/files/copy", strings.NewReader(params.Encode())) req, err := f.client.NewRequest(ctx, "POST", "/v2/files/copy", strings.NewReader(params.Encode()))
if err != nil { if err != nil {
return false, err return false, err
@ -585,7 +594,7 @@ func (f *Fs) Move(ctx context.Context, src fs.Object, remote string) (o fs.Objec
params := url.Values{} params := url.Values{}
params.Set("file_id", strconv.FormatInt(srcObj.file.ID, 10)) params.Set("file_id", strconv.FormatInt(srcObj.file.ID, 10))
params.Set("parent_id", directoryID) params.Set("parent_id", directoryID)
params.Set("name", enc.FromStandardName(leaf)) params.Set("name", f.opt.Enc.FromStandardName(leaf))
req, err := f.client.NewRequest(ctx, "POST", "/v2/files/move", strings.NewReader(params.Encode())) req, err := f.client.NewRequest(ctx, "POST", "/v2/files/move", strings.NewReader(params.Encode()))
if err != nil { if err != nil {
return false, err return false, err
@ -674,7 +683,7 @@ func (f *Fs) DirMove(ctx context.Context, src fs.Fs, srcRemote, dstRemote string
params := url.Values{} params := url.Values{}
params.Set("file_id", srcID) params.Set("file_id", srcID)
params.Set("parent_id", dstDirectoryID) params.Set("parent_id", dstDirectoryID)
params.Set("name", enc.FromStandardName(leaf)) params.Set("name", f.opt.Enc.FromStandardName(leaf))
req, err := f.client.NewRequest(ctx, "POST", "/v2/files/move", strings.NewReader(params.Encode())) req, err := f.client.NewRequest(ctx, "POST", "/v2/files/move", strings.NewReader(params.Encode()))
if err != nil { if err != nil {
return false, err return false, err

View File

@ -137,7 +137,7 @@ func (o *Object) readEntry(ctx context.Context) (f *putio.File, err error) {
} }
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
// fs.Debugf(o, "requesting child. directoryID: %s, name: %s", directoryID, leaf) // fs.Debugf(o, "requesting child. directoryID: %s, name: %s", directoryID, leaf)
req, err := o.fs.client.NewRequest(ctx, "GET", "/v2/files/"+directoryID+"/child?name="+url.QueryEscape(enc.FromStandardName(leaf)), nil) req, err := o.fs.client.NewRequest(ctx, "GET", "/v2/files/"+directoryID+"/child?name="+url.QueryEscape(o.fs.opt.Enc.FromStandardName(leaf)), nil)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -6,10 +6,12 @@ import (
"time" "time"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -25,7 +27,6 @@ canReadUnnormalized = true
canReadRenormalized = true canReadRenormalized = true
canStream = false canStream = false
*/ */
const enc = encodings.Putio
// Constants // Constants
const ( const (
@ -65,9 +66,20 @@ func init() {
log.Fatalf("Failed to configure token: %v", err) log.Fatalf("Failed to configure token: %v", err)
} }
}, },
Options: []fs.Option{{
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Putio,
}},
}) })
} }
// Options defines the configuration for this backend
type Options struct {
Enc encoder.MultiEncoder `config:"encoding"`
}
// Check the interfaces are satisfied // Check the interfaces are satisfied
var ( var (
_ fs.Fs = (*Fs)(nil) _ fs.Fs = (*Fs)(nil)

View File

@ -18,6 +18,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
@ -25,13 +26,12 @@ import (
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/bucket" "github.com/rclone/rclone/lib/bucket"
"github.com/rclone/rclone/lib/encoder"
qsConfig "github.com/yunify/qingstor-sdk-go/v3/config" qsConfig "github.com/yunify/qingstor-sdk-go/v3/config"
qsErr "github.com/yunify/qingstor-sdk-go/v3/request/errors" qsErr "github.com/yunify/qingstor-sdk-go/v3/request/errors"
qs "github.com/yunify/qingstor-sdk-go/v3/service" qs "github.com/yunify/qingstor-sdk-go/v3/service"
) )
const enc = encodings.QingStor
// Register with Fs // Register with Fs
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
@ -113,6 +113,11 @@ and these uploads do not fully utilize your bandwidth, then increasing
this may help to speed up the transfers.`, this may help to speed up the transfers.`,
Default: 1, Default: 1,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.QingStor,
}}, }},
}) })
} }
@ -145,6 +150,7 @@ type Options struct {
UploadCutoff fs.SizeSuffix `config:"upload_cutoff"` UploadCutoff fs.SizeSuffix `config:"upload_cutoff"`
ChunkSize fs.SizeSuffix `config:"chunk_size"` ChunkSize fs.SizeSuffix `config:"chunk_size"`
UploadConcurrency int `config:"upload_concurrency"` UploadConcurrency int `config:"upload_concurrency"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote qingstor server // Fs represents a remote qingstor server
@ -188,7 +194,7 @@ func parsePath(path string) (root string) {
// relative to f.root // relative to f.root
func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) { func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) {
bucketName, bucketPath = bucket.Split(path.Join(f.root, rootRelativePath)) bucketName, bucketPath = bucket.Split(path.Join(f.root, rootRelativePath))
return enc.FromStandardName(bucketName), enc.FromStandardPath(bucketPath) return f.opt.Enc.FromStandardName(bucketName), f.opt.Enc.FromStandardPath(bucketPath)
} }
// split returns bucket and bucketPath from the object // split returns bucket and bucketPath from the object
@ -357,7 +363,7 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
encodedDirectory := enc.FromStandardPath(f.rootDirectory) encodedDirectory := f.opt.Enc.FromStandardPath(f.rootDirectory)
_, err = bucketInit.HeadObject(encodedDirectory, &qs.HeadObjectInput{}) _, err = bucketInit.HeadObject(encodedDirectory, &qs.HeadObjectInput{})
if err == nil { if err == nil {
newRoot := path.Dir(f.root) newRoot := path.Dir(f.root)
@ -555,7 +561,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
continue continue
} }
remote := *commonPrefix remote := *commonPrefix
remote = enc.ToStandardPath(remote) remote = f.opt.Enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -576,7 +582,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
for _, object := range resp.Keys { for _, object := range resp.Keys {
remote := qs.StringValue(object.Key) remote := qs.StringValue(object.Key)
remote = enc.ToStandardPath(remote) remote = f.opt.Enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -653,7 +659,7 @@ func (f *Fs) listBuckets(ctx context.Context) (entries fs.DirEntries, err error)
} }
for _, bucket := range resp.Buckets { for _, bucket := range resp.Buckets {
d := fs.NewDir(enc.ToStandardName(qs.StringValue(bucket.Name)), qs.TimeValue(bucket.Created)) d := fs.NewDir(f.opt.Enc.ToStandardName(qs.StringValue(bucket.Name)), qs.TimeValue(bucket.Created))
entries = append(entries, d) entries = append(entries, d)
} }
return entries, nil return entries, nil

View File

@ -46,6 +46,7 @@ import (
"github.com/ncw/swift" "github.com/ncw/swift"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
@ -54,14 +55,13 @@ import (
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/bucket" "github.com/rclone/rclone/lib/bucket"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
const enc = encodings.S3
// Register with Fs // Register with Fs
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
@ -811,6 +811,11 @@ In Ceph, this can be increased with the "rgw list buckets max chunk" option.
`, `,
Default: 1000, Default: 1000,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.S3,
}}, }},
}) })
} }
@ -853,6 +858,7 @@ type Options struct {
UseAccelerateEndpoint bool `config:"use_accelerate_endpoint"` UseAccelerateEndpoint bool `config:"use_accelerate_endpoint"`
LeavePartsOnError bool `config:"leave_parts_on_error"` LeavePartsOnError bool `config:"leave_parts_on_error"`
ListChunk int64 `config:"list_chunk"` ListChunk int64 `config:"list_chunk"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote s3 server // Fs represents a remote s3 server
@ -965,7 +971,7 @@ func parsePath(path string) (root string) {
// relative to f.root // relative to f.root
func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) { func (f *Fs) split(rootRelativePath string) (bucketName, bucketPath string) {
bucketName, bucketPath = bucket.Split(path.Join(f.root, rootRelativePath)) bucketName, bucketPath = bucket.Split(path.Join(f.root, rootRelativePath))
return enc.FromStandardName(bucketName), enc.FromStandardPath(bucketPath) return f.opt.Enc.FromStandardName(bucketName), f.opt.Enc.FromStandardPath(bucketPath)
} }
// split returns bucket and bucketPath from the object // split returns bucket and bucketPath from the object
@ -1166,7 +1172,7 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
}).Fill(f) }).Fill(f)
if f.rootBucket != "" && f.rootDirectory != "" { if f.rootBucket != "" && f.rootDirectory != "" {
// Check to see if the object exists // Check to see if the object exists
encodedDirectory := enc.FromStandardPath(f.rootDirectory) encodedDirectory := f.opt.Enc.FromStandardPath(f.rootDirectory)
req := s3.HeadObjectInput{ req := s3.HeadObjectInput{
Bucket: &f.rootBucket, Bucket: &f.rootBucket,
Key: &encodedDirectory, Key: &encodedDirectory,
@ -1369,7 +1375,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
continue continue
} }
} }
remote = enc.ToStandardPath(remote) remote = f.opt.Enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -1396,7 +1402,7 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck
continue continue
} }
} }
remote = enc.ToStandardPath(remote) remote = f.opt.Enc.ToStandardPath(remote)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -1487,7 +1493,7 @@ func (f *Fs) listBuckets(ctx context.Context) (entries fs.DirEntries, err error)
return nil, err return nil, err
} }
for _, bucket := range resp.Buckets { for _, bucket := range resp.Buckets {
bucketName := enc.ToStandardName(aws.StringValue(bucket.Name)) bucketName := f.opt.Enc.ToStandardName(aws.StringValue(bucket.Name))
f.cache.MarkOK(bucketName) f.cache.MarkOK(bucketName)
d := fs.NewDir(bucketName, aws.TimeValue(bucket.CreationDate)) d := fs.NewDir(bucketName, aws.TimeValue(bucket.CreationDate))
entries = append(entries, d) entries = append(entries, d)

View File

@ -87,6 +87,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/backend/sharefile/api" "github.com/rclone/rclone/backend/sharefile/api"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
@ -94,6 +95,7 @@ import (
"github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/dircache" "github.com/rclone/rclone/lib/dircache"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/random" "github.com/rclone/rclone/lib/random"
@ -101,8 +103,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.Sharefile
const ( const (
rcloneClientID = "djQUPlHTUM9EvayYBWuKC5IrVIoQde46" rcloneClientID = "djQUPlHTUM9EvayYBWuKC5IrVIoQde46"
rcloneEncryptedClientSecret = "v7572bKhUindQL3yDnUAebmgP-QxiwT38JLxVPolcZBl6SSs329MtFzH73x7BeELmMVZtneUPvALSopUZ6VkhQ" rcloneEncryptedClientSecret = "v7572bKhUindQL3yDnUAebmgP-QxiwT38JLxVPolcZBl6SSs329MtFzH73x7BeELmMVZtneUPvALSopUZ6VkhQ"
@ -204,6 +204,11 @@ be set manually to something like: https://XXX.sharefile.com
`, `,
Advanced: true, Advanced: true,
Default: "", Default: "",
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Sharefile,
}}, }},
}) })
} }
@ -214,6 +219,7 @@ type Options struct {
UploadCutoff fs.SizeSuffix `config:"upload_cutoff"` UploadCutoff fs.SizeSuffix `config:"upload_cutoff"`
ChunkSize fs.SizeSuffix `config:"chunk_size"` ChunkSize fs.SizeSuffix `config:"chunk_size"`
Endpoint string `config:"endpoint"` Endpoint string `config:"endpoint"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote cloud storage system // Fs represents a remote cloud storage system
@ -301,7 +307,7 @@ func (f *Fs) readMetaDataForIDPath(ctx context.Context, id, path string, directo
} }
if path != "" { if path != "" {
opts.Path += "/ByPath" opts.Path += "/ByPath"
opts.Parameters.Set("path", "/"+enc.FromStandardPath(path)) opts.Parameters.Set("path", "/"+f.opt.Enc.FromStandardPath(path))
} }
var item api.Item var item api.Item
var resp *http.Response var resp *http.Response
@ -595,7 +601,7 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (pathIDOut strin
// CreateDir makes a directory with pathID as parent and name leaf // CreateDir makes a directory with pathID as parent and name leaf
func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) { func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (newID string, err error) {
var resp *http.Response var resp *http.Response
leaf = enc.FromStandardName(leaf) leaf = f.opt.Enc.FromStandardName(leaf)
var req = api.Item{ var req = api.Item{
Name: leaf, Name: leaf,
FileName: leaf, FileName: leaf,
@ -664,7 +670,7 @@ func (f *Fs) listAll(ctx context.Context, dirID string, directoriesOnly bool, fi
fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type) fs.Debugf(f, "Ignoring %q - unknown type %q", item.Name, item.Type)
continue continue
} }
item.Name = enc.ToStandardName(item.Name) item.Name = f.opt.Enc.ToStandardName(item.Name)
if fn(item) { if fn(item) {
found = true found = true
break break
@ -873,7 +879,7 @@ func (f *Fs) updateItem(ctx context.Context, id, leaf, directoryID string, modTi
"overwrite": {"false"}, "overwrite": {"false"},
}, },
} }
leaf = enc.FromStandardName(leaf) leaf = f.opt.Enc.FromStandardName(leaf)
// FIXME this appears to be a bug in the API // FIXME this appears to be a bug in the API
// //
// If you set the modified time via PATCH then the server // If you set the modified time via PATCH then the server
@ -1119,7 +1125,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Obj
if err != nil { if err != nil {
return nil, err return nil, err
} }
srcLeaf = enc.FromStandardName(srcLeaf) srcLeaf = f.opt.Enc.FromStandardName(srcLeaf)
_ = srcParentID _ = srcParentID
// Create temporary object // Create temporary object
@ -1127,7 +1133,7 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (dst fs.Obj
if err != nil { if err != nil {
return nil, err return nil, err
} }
dstLeaf = enc.FromStandardName(dstLeaf) dstLeaf = f.opt.Enc.FromStandardName(dstLeaf)
sameName := strings.ToLower(srcLeaf) == strings.ToLower(dstLeaf) sameName := strings.ToLower(srcLeaf) == strings.ToLower(dstLeaf)
if sameName && srcParentID == dstParentID { if sameName && srcParentID == dstParentID {
@ -1390,7 +1396,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
if err != nil { if err != nil {
return err return err
} }
leaf = enc.FromStandardName(leaf) leaf = o.fs.opt.Enc.FromStandardName(leaf)
var req = api.UploadRequest{ var req = api.UploadRequest{
Method: "standard", Method: "standard",
Raw: true, Raw: true,

View File

@ -16,6 +16,7 @@ import (
"github.com/ncw/swift" "github.com/ncw/swift"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/configmap" "github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/configstruct" "github.com/rclone/rclone/fs/config/configstruct"
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
@ -25,6 +26,7 @@ import (
"github.com/rclone/rclone/fs/operations" "github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/bucket" "github.com/rclone/rclone/lib/bucket"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
) )
@ -60,10 +62,13 @@ Rclone will still chunk files bigger than chunk_size when doing normal
copy operations.`, copy operations.`,
Default: false, Default: false,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Swift,
}} }}
const enc = encodings.Swift
// Register with Fs // Register with Fs
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
@ -207,6 +212,7 @@ type Options struct {
EndpointType string `config:"endpoint_type"` EndpointType string `config:"endpoint_type"`
ChunkSize fs.SizeSuffix `config:"chunk_size"` ChunkSize fs.SizeSuffix `config:"chunk_size"`
NoChunk bool `config:"no_chunk"` NoChunk bool `config:"no_chunk"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote swift server // Fs represents a remote swift server
@ -325,7 +331,7 @@ func parsePath(path string) (root string) {
// relative to f.root // relative to f.root
func (f *Fs) split(rootRelativePath string) (container, containerPath string) { func (f *Fs) split(rootRelativePath string) (container, containerPath string) {
container, containerPath = bucket.Split(path.Join(f.root, rootRelativePath)) container, containerPath = bucket.Split(path.Join(f.root, rootRelativePath))
return enc.FromStandardName(container), enc.FromStandardPath(containerPath) return f.opt.Enc.FromStandardName(container), f.opt.Enc.FromStandardPath(containerPath)
} }
// split returns container and containerPath from the object // split returns container and containerPath from the object
@ -446,7 +452,7 @@ func NewFsWithConnection(opt *Options, name, root string, c *swift.Connection, n
// Check to see if the object exists - ignoring directory markers // Check to see if the object exists - ignoring directory markers
var info swift.Object var info swift.Object
var err error var err error
encodedDirectory := enc.FromStandardPath(f.rootDirectory) encodedDirectory := f.opt.Enc.FromStandardPath(f.rootDirectory)
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
var rxHeaders swift.Headers var rxHeaders swift.Headers
info, rxHeaders, err = f.c.Object(f.rootContainer, encodedDirectory) info, rxHeaders, err = f.c.Object(f.rootContainer, encodedDirectory)
@ -559,7 +565,7 @@ func (f *Fs) listContainerRoot(container, directory, prefix string, addContainer
if !recurse { if !recurse {
isDirectory = strings.HasSuffix(object.Name, "/") isDirectory = strings.HasSuffix(object.Name, "/")
} }
remote := enc.ToStandardPath(object.Name) remote := f.opt.Enc.ToStandardPath(object.Name)
if !strings.HasPrefix(remote, prefix) { if !strings.HasPrefix(remote, prefix) {
fs.Logf(f, "Odd name received %q", remote) fs.Logf(f, "Odd name received %q", remote)
continue continue
@ -642,7 +648,7 @@ func (f *Fs) listContainers(ctx context.Context) (entries fs.DirEntries, err err
} }
for _, container := range containers { for _, container := range containers {
f.cache.MarkOK(container.Name) f.cache.MarkOK(container.Name)
d := fs.NewDir(enc.ToStandardName(container.Name), time.Time{}).SetSize(container.Bytes).SetItems(container.Count) d := fs.NewDir(f.opt.Enc.ToStandardName(container.Name), time.Time{}).SetSize(container.Bytes).SetItems(container.Count)
entries = append(entries, d) entries = append(entries, d)
} }
return entries, nil return entries, nil

View File

@ -23,6 +23,7 @@ import (
"github.com/rclone/rclone/fs/encodings" "github.com/rclone/rclone/fs/encodings"
"github.com/rclone/rclone/fs/fserrors" "github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/encoder"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"github.com/rclone/rclone/lib/pacer" "github.com/rclone/rclone/lib/pacer"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
@ -30,8 +31,6 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
const enc = encodings.Yandex
//oAuth //oAuth
const ( const (
rcloneClientID = "ac39b43b9eba4cae8ffb788c06d816a8" rcloneClientID = "ac39b43b9eba4cae8ffb788c06d816a8"
@ -80,6 +79,11 @@ func init() {
Help: "Remove existing public link to file/folder with link command rather than creating.\nDefault is false, meaning link command will create or retrieve public link.", Help: "Remove existing public link to file/folder with link command rather than creating.\nDefault is false, meaning link command will create or retrieve public link.",
Default: false, Default: false,
Advanced: true, Advanced: true,
}, {
Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp,
Advanced: true,
Default: encodings.Yandex,
}}, }},
}) })
} }
@ -88,6 +92,7 @@ func init() {
type Options struct { type Options struct {
Token string `config:"token"` Token string `config:"token"`
Unlink bool `config:"unlink"` Unlink bool `config:"unlink"`
Enc encoder.MultiEncoder `config:"encoding"`
} }
// Fs represents a remote yandex // Fs represents a remote yandex
@ -210,7 +215,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string, options *api.
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("path", enc.FromStandardPath(path)) opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(path))
if options.SortMode != nil { if options.SortMode != nil {
opts.Parameters.Set("sort", options.SortMode.String()) opts.Parameters.Set("sort", options.SortMode.String())
@ -237,7 +242,7 @@ func (f *Fs) readMetaDataForPath(ctx context.Context, path string, options *api.
return nil, err return nil, err
} }
info.Name = enc.ToStandardName(info.Name) info.Name = f.opt.Enc.ToStandardName(info.Name)
return &info, nil return &info, nil
} }
@ -364,7 +369,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
if info.ResourceType == "dir" { if info.ResourceType == "dir" {
//list all subdirs //list all subdirs
for _, element := range info.Embedded.Items { for _, element := range info.Embedded.Items {
element.Name = enc.ToStandardName(element.Name) element.Name = f.opt.Enc.ToStandardName(element.Name)
remote := path.Join(dir, element.Name) remote := path.Join(dir, element.Name)
entry, err := f.itemToDirEntry(ctx, remote, &element) entry, err := f.itemToDirEntry(ctx, remote, &element)
if err != nil { if err != nil {
@ -467,7 +472,7 @@ func (f *Fs) CreateDir(ctx context.Context, path string) (err error) {
if strings.IndexRune(path, ':') >= 0 { if strings.IndexRune(path, ':') >= 0 {
path = "disk:" + path path = "disk:" + path
} }
opts.Parameters.Set("path", enc.FromStandardPath(path)) opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(path))
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
resp, err = f.srv.Call(ctx, &opts) resp, err = f.srv.Call(ctx, &opts)
@ -581,7 +586,7 @@ func (f *Fs) delete(ctx context.Context, path string, hardDelete bool) (err erro
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("path", enc.FromStandardPath(path)) opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(path))
opts.Parameters.Set("permanently", strconv.FormatBool(hardDelete)) opts.Parameters.Set("permanently", strconv.FormatBool(hardDelete))
var resp *http.Response var resp *http.Response
@ -653,8 +658,8 @@ func (f *Fs) copyOrMove(ctx context.Context, method, src, dst string, overwrite
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("from", enc.FromStandardPath(src)) opts.Parameters.Set("from", f.opt.Enc.FromStandardPath(src))
opts.Parameters.Set("path", enc.FromStandardPath(dst)) opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(dst))
opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite)) opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite))
var resp *http.Response var resp *http.Response
@ -803,12 +808,12 @@ func (f *Fs) PublicLink(ctx context.Context, remote string) (link string, err er
} }
opts := rest.Opts{ opts := rest.Opts{
Method: "PUT", Method: "PUT",
Path: enc.FromStandardPath(path), Path: f.opt.Enc.FromStandardPath(path),
Parameters: url.Values{}, Parameters: url.Values{},
NoResponse: true, NoResponse: true,
} }
opts.Parameters.Set("path", enc.FromStandardPath(f.filePath(remote))) opts.Parameters.Set("path", f.opt.Enc.FromStandardPath(f.filePath(remote)))
var resp *http.Response var resp *http.Response
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
@ -994,7 +999,7 @@ func (o *Object) setCustomProperty(ctx context.Context, property string, value s
NoResponse: true, NoResponse: true,
} }
opts.Parameters.Set("path", enc.FromStandardPath(o.filePath())) opts.Parameters.Set("path", o.fs.opt.Enc.FromStandardPath(o.filePath()))
rcm := map[string]interface{}{ rcm := map[string]interface{}{
property: value, property: value,
} }
@ -1031,7 +1036,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("path", enc.FromStandardPath(o.filePath())) opts.Parameters.Set("path", o.fs.opt.Enc.FromStandardPath(o.filePath()))
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl) resp, err = o.fs.srv.CallJSON(ctx, &opts, nil, &dl)
@ -1068,7 +1073,7 @@ func (o *Object) upload(ctx context.Context, in io.Reader, overwrite bool, mimeT
Parameters: url.Values{}, Parameters: url.Values{},
} }
opts.Parameters.Set("path", enc.FromStandardPath(o.filePath())) opts.Parameters.Set("path", o.fs.opt.Enc.FromStandardPath(o.filePath()))
opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite)) opts.Parameters.Set("overwrite", strconv.FormatBool(overwrite))
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {

View File

@ -60,6 +60,12 @@ const (
// ConfigTokenURL is the config key used to store the token server endpoint // ConfigTokenURL is the config key used to store the token server endpoint
ConfigTokenURL = "token_url" ConfigTokenURL = "token_url"
// ConfigEncoding is the config key to change the encoding for a backend
ConfigEncoding = "encoding"
// ConfigEncodingHelp is the help for ConfigEncoding
ConfigEncodingHelp = "This sets the encoding for the backend.\n\nSee: the [encoding section in the overview](/overview/#encoding) for more info."
// ConfigAuthorize indicates that we just want "rclone authorize" // ConfigAuthorize indicates that we just want "rclone authorize"
ConfigAuthorize = "config_authorize" ConfigAuthorize = "config_authorize"

View File

@ -1,4 +1,4 @@
package config package config_test
import ( import (
"context" "context"
@ -6,6 +6,7 @@ import (
_ "github.com/rclone/rclone/backend/local" _ "github.com/rclone/rclone/backend/local"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/obscure" "github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/rc" "github.com/rclone/rclone/fs/rc"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -28,8 +29,8 @@ func TestRc(t *testing.T) {
out, err := call.Fn(context.Background(), in) out, err := call.Fn(context.Background(), in)
require.NoError(t, err) require.NoError(t, err)
require.Nil(t, out) require.Nil(t, out)
assert.Equal(t, "local", FileGet(testName, "type")) assert.Equal(t, "local", config.FileGet(testName, "type"))
assert.Equal(t, "sausage", FileGet(testName, "test_key")) assert.Equal(t, "sausage", config.FileGet(testName, "test_key"))
// The sub tests rely on the remote created above but they can // The sub tests rely on the remote created above but they can
// all be run independently // all be run independently
@ -92,9 +93,9 @@ func TestRc(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Nil(t, out) assert.Nil(t, out)
assert.Equal(t, "local", FileGet(testName, "type")) assert.Equal(t, "local", config.FileGet(testName, "type"))
assert.Equal(t, "rutabaga", FileGet(testName, "test_key")) assert.Equal(t, "rutabaga", config.FileGet(testName, "test_key"))
assert.Equal(t, "cabbage", FileGet(testName, "test_key2")) assert.Equal(t, "cabbage", config.FileGet(testName, "test_key2"))
}) })
t.Run("Password", func(t *testing.T) { t.Run("Password", func(t *testing.T) {
@ -111,9 +112,9 @@ func TestRc(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Nil(t, out) assert.Nil(t, out)
assert.Equal(t, "local", FileGet(testName, "type")) assert.Equal(t, "local", config.FileGet(testName, "type"))
assert.Equal(t, "rutabaga", obscure.MustReveal(FileGet(testName, "test_key"))) assert.Equal(t, "rutabaga", obscure.MustReveal(config.FileGet(testName, "test_key")))
assert.Equal(t, "cabbage", obscure.MustReveal(FileGet(testName, "test_key2"))) assert.Equal(t, "cabbage", obscure.MustReveal(config.FileGet(testName, "test_key2")))
}) })
// Delete the test remote // Delete the test remote
@ -125,8 +126,8 @@ func TestRc(t *testing.T) {
out, err = call.Fn(context.Background(), in) out, err = call.Fn(context.Background(), in)
require.NoError(t, err) require.NoError(t, err)
assert.Nil(t, out) assert.Nil(t, out)
assert.Equal(t, "", FileGet(testName, "type")) assert.Equal(t, "", config.FileGet(testName, "type"))
assert.Equal(t, "", FileGet(testName, "test_key")) assert.Equal(t, "", config.FileGet(testName, "test_key"))
} }
func TestRcProviders(t *testing.T) { func TestRcProviders(t *testing.T) {