Skip to content

Commit 8e7368f

Browse files
authored
Merge branch 'rclone:master' into master
2 parents ee51a80 + 96760f1 commit 8e7368f

65 files changed

Lines changed: 1947 additions & 374 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ directories to and from different cloud storage providers.
5959
- Hetzner Object Storage [:page_facing_up:](https://rclone.org/s3/#hetzner)
6060
- Hetzner Storage Box [:page_facing_up:](https://rclone.org/sftp/#hetzner-storage-box)
6161
- HiDrive [:page_facing_up:](https://rclone.org/hidrive/)
62+
- Hitachi Content Platform (HCP) [:page_facing_up:](https://rclone.org/s3/#hcp)
6263
- HTTP [:page_facing_up:](https://rclone.org/http/)
6364
- Huawei Cloud Object Storage Service(OBS) [:page_facing_up:](https://rclone.org/s3/#huawei-obs)
6465
- iCloud Drive [:page_facing_up:](https://rclone.org/iclouddrive/)

backend/azurefiles/azurefiles.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ func newFsFromOptions(ctx context.Context, name, root string, opt *Options) (fs.
173173
NewSharedKeyCredential: service.NewSharedKeyCredential,
174174
SetClientOptions: func(options *service.ClientOptions, policyClientOptions policy.ClientOptions) {
175175
options.ClientOptions = policyClientOptions
176+
// FileRequestIntent is required for TokenCredential (OAuth)
177+
// auth; the SDK omits it for shared-key / SAS.
178+
backup := service.ShareTokenIntentBackup
179+
options.FileRequestIntent = &backup
176180
},
177181
}
178182
res, err := auth.NewClient(ctx, conf, &opt.Options)

backend/cache/cache.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -563,9 +563,10 @@ Eg
563563
})
564564

565565
rc.Add(rc.Call{
566-
Path: "cache/stats",
567-
Fn: f.httpStats,
568-
Title: "Get cache stats",
566+
Path: "cache/stats",
567+
NoAuth: true,
568+
Fn: f.httpStats,
569+
Title: "Get cache stats",
569570
Help: `
570571
Show statistics for the cache remote.
571572
`,

backend/iclouddrive/iclouddrive_unsupported.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
//go:build plan9 || solaris
55

66
// Package iclouddrive implements the iCloud Drive backend
7-
package iclouddrive
7+
package iclouddrive

backend/internxt/internxt.go

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,21 @@ import (
3030
"github.com/rclone/rclone/fs/hash"
3131
"github.com/rclone/rclone/lib/dircache"
3232
"github.com/rclone/rclone/lib/encoder"
33+
"github.com/rclone/rclone/lib/multipart"
3334
"github.com/rclone/rclone/lib/oauthutil"
3435
"github.com/rclone/rclone/lib/pacer"
3536
"github.com/rclone/rclone/lib/random"
3637
)
3738

3839
const (
39-
minSleep = 10 * time.Millisecond
40-
maxSleep = 2 * time.Second
41-
decayConstant = 2 // bigger for slower decay, exponential
40+
minSleep = 10 * time.Millisecond
41+
maxSleep = 2 * time.Second
42+
decayConstant = 2
43+
maxUploadParts = 10000
44+
minChunkSize = fs.SizeSuffix(5 * 1024 * 1024)
45+
minUploadCutoff = fs.SizeSuffix(100 * 1024 * 1024)
46+
defaultUploadCutoff = fs.SizeSuffix(100 * 1024 * 1024)
47+
maxUploadCutoff = fs.SizeSuffix(5 * 1024 * 1024 * 1024)
4248
)
4349

4450
// shouldRetry determines if an error should be retried.
@@ -101,6 +107,21 @@ func init() {
101107
Default: true,
102108
Advanced: true,
103109
Help: "Skip hash validation when downloading files.\n\nBy default, hash validation is disabled. Set this to false to enable validation.",
110+
}, {
111+
Name: "upload_concurrency",
112+
Help: "Concurrency for multipart uploads.\n\nThis is the number of chunks of the same file that are uploaded concurrently.\n\nNote that each chunk is buffered in memory.",
113+
Default: 4,
114+
Advanced: true,
115+
}, {
116+
Name: "upload_cutoff",
117+
Help: "Cutoff for switching to multipart upload.\n\nAny files larger than this will be uploaded in chunks of chunk_size.\nThe minimum is 100 MiB and the maximum is 5 GiB.",
118+
Default: defaultUploadCutoff,
119+
Advanced: true,
120+
}, {
121+
Name: "chunk_size",
122+
Help: "Chunk size for multipart uploads.\n\nFiles larger than upload_cutoff will be uploaded in chunks of this size.\n\nMemory usage is approximately chunk_size * upload_concurrency.",
123+
Default: fs.SizeSuffix(30 * 1024 * 1024),
124+
Advanced: true,
104125
}, {
105126
Name: rclone_config.ConfigEncoding,
106127
Help: rclone_config.ConfigEncodingHelp,
@@ -194,6 +215,9 @@ type Options struct {
194215
TwoFA string `config:"2fa"`
195216
Mnemonic string `config:"mnemonic"`
196217
SkipHashValidation bool `config:"skip_hash_validation"`
218+
UploadCutoff fs.SizeSuffix `config:"upload_cutoff"`
219+
ChunkSize fs.SizeSuffix `config:"chunk_size"`
220+
UploadConcurrency int `config:"upload_concurrency"`
197221
Encoding encoder.MultiEncoder `config:"encoding"`
198222
}
199223

@@ -238,6 +262,11 @@ func (f *Fs) Features() *fs.Features {
238262
return f.features
239263
}
240264

265+
// DirCacheFlush resets the directory cache
266+
func (f *Fs) DirCacheFlush() {
267+
f.dirCache.ResetRoot()
268+
}
269+
241270
// Hashes returns type of hashes supported by Internxt
242271
func (f *Fs) Hashes() hash.Set {
243272
return hash.NewHashSet()
@@ -255,6 +284,13 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
255284
return nil, err
256285
}
257286

287+
if err := checkUploadChunkSize(opt.ChunkSize); err != nil {
288+
return nil, fmt.Errorf("internxt: chunk size: %w", err)
289+
}
290+
if err := checkUploadCutoff(opt.UploadCutoff); err != nil {
291+
return nil, fmt.Errorf("internxt: upload cutoff: %w", err)
292+
}
293+
258294
if opt.Mnemonic == "" {
259295
return nil, errors.New("mnemonic is required - please run: rclone config reconnect " + name + ":")
260296
}
@@ -884,32 +920,53 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
884920
fs.Debugf(o.f, "Renamed existing file %s to backup %s.%s (UUID: %s)", remote, backupName, backupType, backupUUID)
885921
}
886922

923+
size := src.Size()
924+
887925
var meta *buckets.CreateMetaResponse
888-
err = o.f.pacer.CallNoRetry(func() (bool, error) {
889-
var err error
890-
meta, err = buckets.UploadFileStreamAuto(ctx,
891-
o.f.cfg,
892-
dirID,
893-
o.f.opt.Encoding.FromStandardName(path.Base(remote)),
894-
in,
895-
src.Size(),
896-
src.ModTime(ctx),
897-
)
898-
return o.f.shouldRetry(ctx, err)
899-
})
926+
if size < 0 || size >= int64(o.f.opt.UploadCutoff) {
927+
chunkWriter, uploadErr := multipart.UploadMultipart(ctx, src, in, multipart.UploadMultipartOptions{
928+
Open: o.f,
929+
OpenOptions: options,
930+
})
900931

901-
if err != nil && isEmptyFileLimitError(err) {
902-
o.restoreBackupFile(ctx, backupUUID, origName, origType)
903-
return fs.ErrorCantUploadEmptyFiles
904-
}
932+
if uploadErr != nil {
933+
if isEmptyFileLimitError(uploadErr) {
934+
o.restoreBackupFile(ctx, backupUUID, origName, origType)
935+
return fs.ErrorCantUploadEmptyFiles
936+
}
937+
o.restoreBackupFile(ctx, backupUUID, origName, origType)
938+
return uploadErr
939+
}
940+
w := chunkWriter.(*internxtChunkWriter)
941+
meta = w.meta
942+
} else {
943+
// Use single-part upload for small files
944+
err = o.f.pacer.CallNoRetry(func() (bool, error) {
945+
var err error
946+
meta, err = buckets.UploadFileStreamAuto(ctx,
947+
o.f.cfg,
948+
dirID,
949+
o.f.opt.Encoding.FromStandardName(path.Base(remote)),
950+
in,
951+
size,
952+
src.ModTime(ctx),
953+
)
954+
return o.f.shouldRetry(ctx, err)
955+
})
905956

906-
if err != nil {
907-
meta, err = o.recoverFromTimeoutConflict(ctx, err, remote, dirID)
908-
}
957+
if err != nil && isEmptyFileLimitError(err) {
958+
o.restoreBackupFile(ctx, backupUUID, origName, origType)
959+
return fs.ErrorCantUploadEmptyFiles
960+
}
909961

910-
if err != nil {
911-
o.restoreBackupFile(ctx, backupUUID, origName, origType)
912-
return err
962+
if err != nil {
963+
meta, err = o.recoverFromTimeoutConflict(ctx, err, remote, dirID)
964+
}
965+
966+
if err != nil {
967+
o.restoreBackupFile(ctx, backupUUID, origName, origType)
968+
return err
969+
}
913970
}
914971

915972
// Update object metadata

backend/internxt/internxt_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ package internxt_test
33
import (
44
"testing"
55

6+
"github.com/rclone/rclone/fs"
67
"github.com/rclone/rclone/fstest/fstests"
78
)
89

910
// TestIntegration runs integration tests against the remote
1011
func TestIntegration(t *testing.T) {
1112
fstests.Run(t, &fstests.Opt{
1213
RemoteName: "TestInternxt:",
14+
ChunkedUpload: fstests.ChunkedUploadConfig{
15+
MinChunkSize: 100 * fs.Mebi,
16+
NeedMultipleChunks: true,
17+
},
1318
})
1419
}

0 commit comments

Comments
 (0)