1
0
mirror of https://github.com/rclone/rclone.git synced 2026-02-25 08:53:37 +00:00

Compare commits

...

50 Commits

Author SHA1 Message Date
Nick Craig-Wood
699e4cfb23 docs: update sponsors 2026-02-23 13:05:33 +00:00
Nick Craig-Wood
bc5cad8792 Add Jan-Philipp Reßler to contributors 2026-02-23 13:05:25 +00:00
Nick Craig-Wood
9024962fc4 Add Chris to contributors 2026-02-23 13:05:25 +00:00
Nick Craig-Wood
aa1f4ace64 Add Shlomi Avihou to contributors 2026-02-23 13:05:25 +00:00
Nick Craig-Wood
dd1d750c55 Add Jan-Philipp Reßler to contributors 2026-02-23 13:05:25 +00:00
Nick Craig-Wood
5a2564c6e2 Add Varun Chawla to contributors 2026-02-23 13:05:24 +00:00
Nick Craig-Wood
82cc80cc6f Add Prakhar Chhalotre to contributors 2026-02-23 13:05:24 +00:00
Chris
7d0a8bf850 s3: add Object Lock support
Add support for S3 Object Lock with the following new options:

- --s3-object-lock-mode: set retention mode (GOVERNANCE/COMPLIANCE/copy)
- --s3-object-lock-retain-until-date: set retention date (RFC3339/duration/copy)
- --s3-object-lock-legal-hold-status: set legal hold (ON/OFF/copy)
- --s3-bypass-governance-retention: bypass GOVERNANCE lock on delete
- --s3-bucket-object-lock-enabled: enable Object Lock on bucket creation
- --s3-object-lock-set-after-upload: apply lock via separate API calls

The special value "copy" preserves the source object's setting when used
with --metadata flag, enabling scenarios like cloning objects from
COMPLIANCE to GOVERNANCE mode while preserving the original retention date.

Includes integration tests that create a temporary Object Lock bucket covering:
- Retention Mode and Date
- Legal Hold
- Apply settings after upload
- Override protections using bypass-governance flag
The tests are gracefully skipped on providers that do not support Object Lock.

Fixes #4683
Closes #7894 #7893 #8866
2026-02-20 16:40:24 +00:00
Varun Chawla
fd8b28d36d webdav: escape reserved characters in URL path segments
Use URLPathEscapeAll instead of URLPathEscape for path encoding.

URLPathEscape relies on Go's url.URL.String() which only minimally
escapes paths - reserved sub-delimiter characters like semicolons and
equals signs pass through unescaped. Per RFC 3986 section 3.3, these
characters must be percent-encoded when used as literal values in
path segments.

Some WebDAV servers (notably dCache/Jetty) interpret unescaped
semicolons as path parameter delimiters, which truncates filenames
at the semicolon position. URLPathEscapeAll encodes everything
except [A-Za-z0-9/], which is safe for all servers.

Fixes #9082
2026-02-20 16:30:15 +00:00
Shlomi Avihou
c63ecace41 s3: add Zadara Object Storage provider 2026-02-20 16:27:17 +00:00
Jan-Philipp Reßler
0c8c3d8fb9 bisync: add group Sync to the bisync command
Co-authored-by: Jan-Philipp Reßler <xodarap@xodarap.de>
2026-02-20 16:21:48 +00:00
Varun Chawla
5042f360f0 archive: extract: strip "./" prefix from tar entry paths
Tar files created from the current directory (e.g. tar -czf archive.tar.gz .)
produce entries prefixed with "./". When extracting, rclone's character
encoding replaces the "." with a full-width dot (U+FF0E), creating a
spurious directory instead of merging into the destination root.

Strip the leading "./" from NameInArchive before processing. Only "./"
is stripped specifically to avoid enabling path traversal attacks via
"../".

Fixes #9168
2026-02-20 11:46:53 +00:00
Prakhar Chhalotre
9601dbce87 accounting: update String method output format for clarity in transfer rate representation - fixes #9129 2026-02-18 15:19:47 +00:00
Nick Craig-Wood
e06f0b0595 docs: add instructions on how to update Go version 2026-02-18 12:38:50 +00:00
Nick Craig-Wood
b2866f0291 build: modernize Go code with go fix for go1.25 2026-02-18 12:11:52 +00:00
Nick Craig-Wood
cf97f250df build: update all dependencies
Could not update github.com/coreos/go-systemd as the new version doesn't build under freebsd

See: https://github.com/coreos/go-systemd/issues/509
2026-02-18 11:33:48 +00:00
Nick Craig-Wood
627b763d4b lib/rest: remove go1.24 workaround now go1.25 is the minimum 2026-02-18 11:33:48 +00:00
Nick Craig-Wood
f14945f9c1 build: update to go1.26 and make go1.25 the minimum required version 2026-02-18 11:33:48 +00:00
Nick Craig-Wood
391661fdb4 Add Jack Kelly to contributors 2026-02-18 11:33:48 +00:00
Nick Craig-Wood
faffd0a6f1 Changelog updates from Version v1.73.1 2026-02-17 18:22:21 +00:00
Nick Craig-Wood
6cc3356f8e build: fix build using go 1.26.0 instead of go 1.25.7
In the actions config use Go ~1.25.7 to pin the go version to 1.25.x,
x >= 7.

Before this it was choosing Go 1.26.0 which isn't what we want.
2026-02-17 17:05:45 +00:00
Nick Craig-Wood
07e76419c9 fs/march: fix runtime: program exceeds 10000-thread limit
Before this change when doing a sync with `--no-traverse` and
`--files-from` we could call `NewObject` a total of `--checkers` *
`--checkers` times simultaneously.

With `--checkers 128` this can exceed the 10,000 thread limit and
fails when run on a local to local transfer because `NewObject` calls
`lstat` which is a syscall which needs an OS thread of its own.

This patch uses a weighted semaphore to limit the number of
simultaneous calls to `NewObject` to `--checkers` instead which won't
blow the 10,000 thread limit and is far more sensible use of OS
resources.

Fixes #9073
2026-02-17 12:27:17 +00:00
Nick Craig-Wood
60c4f35b56 accounting: fix missing server side stats from core/stats rc
These stats weren't being updated in the global stats read by rc
core/stats:

- transferQueue
- deletesSize
- serverSideCopies
- serverSideCopyBytes
- serverSideMoves
- serverSideMoveBytes
2026-02-17 12:27:17 +00:00
Nick Craig-Wood
15a9c0fd36 pacer: re-read the sleep time as it may be stale
Before this change we read sleepTime before acquiring the pacer token
and uses that possibly stale value to schedule the token return. When
many goroutines enter while sleepTime is high (e.g., 10s), each
goroutine caches this 10s value. Even if successful calls rapidly
decay the pacer state to 0, the queued goroutines still schedule 10s
token returns, so the queue drains at 1 req/10s for the entire herd.
This can create multi‑minute delays even after the pacer has dropped
to 0.

After this change we refresh the sleep time after getting the token.

This problem was introduced by the desire to skip reading the pacer
token entirely when sleepTime is 0 in high performance backends (eg
s3, azure blob).
2026-02-17 12:27:17 +00:00
Nick Craig-Wood
8b85ffbf03 pacer: fix deadlock between pacer token and --max-connections
It was possible in the presence of --max-connections and recursive
calls to the pacer to deadlock it leaving all connections waiting on
either a max connection token or a pacer token.

This fixes the problem by making sure we return the pacer token on
schedule if we take it.

This also short circuits the pacer token if sleepTime is 0.
2026-02-17 12:27:17 +00:00
Nick Craig-Wood
26fb659fe4 test_all: increase retries for Internxt eventual consistency 2026-02-17 12:27:17 +00:00
Nick Craig-Wood
7aa3d8a32f build: fix CVE-2025-68121 by updating go to 1.25.7 or later - fixes #9167 2026-02-17 12:27:17 +00:00
Nick Craig-Wood
b7ebec865b drime: fix files and directories being created in the default workspace
Before this change directories and files were created in the default
workspace, not the workspace specified by --drime-workspace-id.
2026-02-17 12:27:17 +00:00
Nick Craig-Wood
a60d09c43d docs: update sponsors 2026-02-17 12:27:04 +00:00
Nick Craig-Wood
14a47937c0 Add kingston125 to contributors 2026-02-17 12:22:11 +00:00
Jack Kelly
64d6916161 copyurl: Extend copyurl docs with an example of CSV FILENAMEs starting with a path. 2026-02-17 11:46:37 +00:00
kingston125
ae778f1413 filelu: migrate API calls to lib/rest 2026-02-16 17:45:42 +00:00
José Zúniga
33859568d6 internxt: implement re-login under refresh logic, improve retry logic - fixes #9174 2026-02-13 19:18:51 +00:00
Nick Craig-Wood
4b3aa5aea0 docs: add ExchangeRate-API as a sponsor 2026-02-12 14:08:20 +00:00
Nick Craig-Wood
349487bb7f Add Cohinem to contributors 2026-02-12 14:08:20 +00:00
Nick Craig-Wood
b70b2fff16 Add Leon Brocard to contributors 2026-02-12 14:08:20 +00:00
Leon Brocard
32307e9226 s3: remove StackPath Object Storage provider
StackPath's object storage service no longer exists and all S3
endpoints are no longer operational.

Before this change, users could select StackPath as an S3 provider
during configuration, but connections would fail as the endpoints no
longer respond and the service has been discontinued.

After this change, StackPath is removed from the list of supported
S3 providers, preventing users from attempting to configure a
non-functional service.

Fixes #9148
2026-02-11 23:12:35 +01:00
Cohinem
2bd6630c2e drime: implement About 2026-02-11 14:06:31 +00:00
albertony
54c2078f25 build: bump github.com/go-chi/chi/v5 from 5.2.3 to 5.2.5 to fix GO-2026-4316 2026-02-11 11:34:29 +01:00
kingston125
4f284614a4 Set list_version to 2 for FileLu S3 configuration 2026-02-11 11:32:11 +01:00
kingston125
eef0b39a2c filelu: add multipart upload support with configurable cutoff 2026-02-05 13:09:16 +00:00
kingston125
37f6336636 filelu: add multipart init response type 2026-02-05 13:09:16 +00:00
kingston125
1049f88a1d filelu: add comment for response body wrapping 2026-02-05 13:09:16 +00:00
kingston125
327ca25a4d filelu: avoid buffering entire file in memory
Avoid buffering the entire file in memory during download, especially
for large files.
2026-02-05 13:09:16 +00:00
Nick Craig-Wood
673e24a60f docs: update sponsor logos 2026-02-05 12:27:51 +00:00
Leon Brocard
43db4c5dc7 s3: add Fastly Object Storage provider
- Add new Fastly provider with US East, US West, and EU Central regions
- Add `etag_is_not_md5` quirk for providers with mandatory encryption
- Disable server-side copy for Fastly (not supported)
2026-02-05 12:10:53 +00:00
Enduriel
88b484722a filen: fix potential panic in case of error during upload 2026-02-05 12:08:20 +00:00
Enduriel
ed5bd327c0 filen: fix 32 bit targets not being able to list directories Fixes #9142
or do pretty much anything,
this was caused by timestamps not being read to 64 bit integers
2026-02-05 12:06:20 +00:00
wiserain
341ce61a2a pikpak: support custom filenames for addurl backend command - fixes #9111
Extended the addurl backend command to accept an optional filename parameter, 
enabling customized naming for downloaded files.
2026-02-03 08:13:03 +09:00
Nick Craig-Wood
9abf9d38c0 Start v1.74.0-DEV development 2026-01-30 22:19:04 +00:00
84 changed files with 2315 additions and 877 deletions

View File

@@ -29,12 +29,12 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
job_name: ['linux', 'linux_386', 'mac_amd64', 'mac_arm64', 'windows', 'other_os', 'go1.24'] job_name: ['linux', 'linux_386', 'mac_amd64', 'mac_arm64', 'windows', 'other_os', 'go1.25']
include: include:
- job_name: linux - job_name: linux
os: ubuntu-latest os: ubuntu-latest
go: '>=1.25.0-rc.1' go: '~1.26.0'
gotags: cmount gotags: cmount
build_flags: '-include "^linux/"' build_flags: '-include "^linux/"'
check: true check: true
@@ -45,14 +45,14 @@ jobs:
- job_name: linux_386 - job_name: linux_386
os: ubuntu-latest os: ubuntu-latest
go: '>=1.25.0-rc.1' go: '~1.26.0'
goarch: 386 goarch: 386
gotags: cmount gotags: cmount
quicktest: true quicktest: true
- job_name: mac_amd64 - job_name: mac_amd64
os: macos-latest os: macos-latest
go: '>=1.25.0-rc.1' go: '~1.26.0'
gotags: 'cmount' gotags: 'cmount'
build_flags: '-include "^darwin/amd64" -cgo' build_flags: '-include "^darwin/amd64" -cgo'
quicktest: true quicktest: true
@@ -61,14 +61,14 @@ jobs:
- job_name: mac_arm64 - job_name: mac_arm64
os: macos-latest os: macos-latest
go: '>=1.25.0-rc.1' go: '~1.26.0'
gotags: 'cmount' gotags: 'cmount'
build_flags: '-include "^darwin/arm64" -cgo -macos-arch arm64 -cgo-cflags=-I/usr/local/include -cgo-ldflags=-L/usr/local/lib' build_flags: '-include "^darwin/arm64" -cgo -macos-arch arm64 -cgo-cflags=-I/usr/local/include -cgo-ldflags=-L/usr/local/lib'
deploy: true deploy: true
- job_name: windows - job_name: windows
os: windows-latest os: windows-latest
go: '>=1.25.0-rc.1' go: '~1.26.0'
gotags: cmount gotags: cmount
cgo: '0' cgo: '0'
build_flags: '-include "^windows/"' build_flags: '-include "^windows/"'
@@ -78,14 +78,14 @@ jobs:
- job_name: other_os - job_name: other_os
os: ubuntu-latest os: ubuntu-latest
go: '>=1.25.0-rc.1' go: '~1.26.0'
build_flags: '-exclude "^(windows/|darwin/|linux/)"' build_flags: '-exclude "^(windows/|darwin/|linux/)"'
compile_all: true compile_all: true
deploy: true deploy: true
- job_name: go1.24 - job_name: go1.25
os: ubuntu-latest os: ubuntu-latest
go: '1.24' go: '~1.25.7'
quicktest: true quicktest: true
racequicktest: true racequicktest: true
@@ -224,7 +224,7 @@ jobs:
id: setup-go id: setup-go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '>=1.24.0-rc.1' go-version: '~1.26.0'
check-latest: true check-latest: true
cache: false cache: false
@@ -315,7 +315,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '>=1.25.0-rc.1' go-version: '~1.26.0'
- name: Set global environment variables - name: Set global environment variables
run: | run: |

View File

@@ -44,6 +44,7 @@ directories to and from different cloud storage providers.
- Dropbox [:page_facing_up:](https://rclone.org/dropbox/) - Dropbox [:page_facing_up:](https://rclone.org/dropbox/)
- Enterprise File Fabric [:page_facing_up:](https://rclone.org/filefabric/) - Enterprise File Fabric [:page_facing_up:](https://rclone.org/filefabric/)
- Exaba [:page_facing_up:](https://rclone.org/s3/#exaba) - Exaba [:page_facing_up:](https://rclone.org/s3/#exaba)
- Fastly Object Storage [:page_facing_up:](https://rclone.org/s3/#fastly)
- Fastmail Files [:page_facing_up:](https://rclone.org/webdav/#fastmail-files) - Fastmail Files [:page_facing_up:](https://rclone.org/webdav/#fastmail-files)
- FileLu [:page_facing_up:](https://rclone.org/filelu/) - FileLu [:page_facing_up:](https://rclone.org/filelu/)
- Filen [:page_facing_up:](https://rclone.org/filen/) - Filen [:page_facing_up:](https://rclone.org/filen/)
@@ -117,7 +118,6 @@ directories to and from different cloud storage providers.
- Shade [:page_facing_up:](https://rclone.org/shade/) - Shade [:page_facing_up:](https://rclone.org/shade/)
- SMB / CIFS [:page_facing_up:](https://rclone.org/smb/) - SMB / CIFS [:page_facing_up:](https://rclone.org/smb/)
- Spectra Logic [:page_facing_up:](https://rclone.org/s3/#spectralogic) - Spectra Logic [:page_facing_up:](https://rclone.org/s3/#spectralogic)
- StackPath [:page_facing_up:](https://rclone.org/s3/#stackpath)
- Storj [:page_facing_up:](https://rclone.org/storj/) - Storj [:page_facing_up:](https://rclone.org/storj/)
- SugarSync [:page_facing_up:](https://rclone.org/sugarsync/) - SugarSync [:page_facing_up:](https://rclone.org/sugarsync/)
- Synology C2 Object Storage [:page_facing_up:](https://rclone.org/s3/#synology-c2) - Synology C2 Object Storage [:page_facing_up:](https://rclone.org/s3/#synology-c2)
@@ -126,6 +126,7 @@ directories to and from different cloud storage providers.
- Wasabi [:page_facing_up:](https://rclone.org/s3/#wasabi) - Wasabi [:page_facing_up:](https://rclone.org/s3/#wasabi)
- WebDAV [:page_facing_up:](https://rclone.org/webdav/) - WebDAV [:page_facing_up:](https://rclone.org/webdav/)
- Yandex Disk [:page_facing_up:](https://rclone.org/yandex/) - Yandex Disk [:page_facing_up:](https://rclone.org/yandex/)
- Zadara Object Storage [:page_facing_up:](https://rclone.org/s3/#zadara)
- Zoho WorkDrive [:page_facing_up:](https://rclone.org/zoho/) - Zoho WorkDrive [:page_facing_up:](https://rclone.org/zoho/)
- Zata.ai [:page_facing_up:](https://rclone.org/s3/#Zata) - Zata.ai [:page_facing_up:](https://rclone.org/s3/#Zata)
- The local filesystem [:page_facing_up:](https://rclone.org/local/) - The local filesystem [:page_facing_up:](https://rclone.org/local/)

View File

@@ -109,6 +109,59 @@ go run github.com/icholy/gomajor@latest list -major
Expect API breakage when updating major versions. Expect API breakage when updating major versions.
## Updating Go
When a new Go stable is released update to it. We support the current
stable Go and the previous release which is in line with the rest of
the Go ecosystem.
These files will need editing:
- `.github/workflows/build.yml` - change current and previous Go versions
- `docs/content/install.md` - change minimum Go version required
- `fs/versioncheck.go` - update minimum Go version required
- `go.mod` - update minimum Go version required
Check it builds
- `make GOTAGS=cmount`
- `make compiletest`
Assuming `go1.XX` is current and `go1.YY` is previous version:
Use `git grep go1.YY` and `git grep go1.YY` to look for opportunities
to remove build tags we no longer need.
Commit with message like this:
```text
build: update to go1.YY and make go1.YY the minimum required version
```
Send to CI and if it passes, merge.
### gofix
Updating the minimum required version of Go is a good opportunity to
run the `go fix` command to modernize Go usage.
This needs to be run for all architectures.
```console
GOOS=linux go fix -tags cmount ./...
GOOS=freebsd go fix -tags cmount ./...
GOOS=windows go fix -tags cmount ./...
GOOS=darwin go fix -tags cmount ./...
```
Examine the diff carefully.
Commit with message
```text
build: modernize Go code with go fix for go1.YY
```
## Tidy beta ## Tidy beta
At some point after the release run At some point after the release run

View File

@@ -1 +1 @@
v1.73.0 v1.74.0

View File

@@ -14,6 +14,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"maps"
"net/http" "net/http"
"path" "path"
"slices" "slices"
@@ -729,8 +730,8 @@ func parseXMsTags(s string) (map[string]string, error) {
return map[string]string{}, nil return map[string]string{}, nil
} }
out := make(map[string]string) out := make(map[string]string)
parts := strings.Split(s, ",") parts := strings.SplitSeq(s, ",")
for _, p := range parts { for p := range parts {
p = strings.TrimSpace(p) p = strings.TrimSpace(p)
if p == "" { if p == "" {
continue continue
@@ -893,9 +894,7 @@ func assembleCopyParams(ctx context.Context, f *Fs, src fs.Object, srcProps *blo
if meta == nil { if meta == nil {
meta = make(map[string]*string, len(userMeta)) meta = make(map[string]*string, len(userMeta))
} }
for k, v := range userMeta { maps.Copy(meta, userMeta)
meta[k] = v
}
} }
// Apply tags if any // Apply tags if any
if len(mappedTags) > 0 { if len(mappedTags) > 0 {
@@ -992,9 +991,7 @@ func (o *Object) applyMappedMetadata(ctx context.Context, src fs.ObjectInfo, ui
if o.tags == nil { if o.tags == nil {
o.tags = make(map[string]string, len(tags)) o.tags = make(map[string]string, len(tags))
} }
for k, v := range tags { maps.Copy(o.tags, tags)
o.tags[k] = v
}
} }
if mappedModTime != nil { if mappedModTime != nil {
@@ -1859,9 +1856,7 @@ func (f *Fs) copySinglepart(ctx context.Context, remote, dstContainer, dstPath s
// Apply tags and post-copy headers only when mapping requested changes // Apply tags and post-copy headers only when mapping requested changes
if len(tags) > 0 { if len(tags) > 0 {
options.BlobTags = make(map[string]string, len(tags)) options.BlobTags = make(map[string]string, len(tags))
for k, v := range tags { maps.Copy(options.BlobTags, tags)
options.BlobTags[k] = v
}
} }
if hadMapping { if hadMapping {
// Only set metadata explicitly when mapping was requested; otherwise // Only set metadata explicitly when mapping was requested; otherwise
@@ -2062,9 +2057,7 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
// Merge user metadata (already lower-cased keys) // Merge user metadata (already lower-cased keys)
metadataMu.Lock() metadataMu.Lock()
for k, v := range o.meta { maps.Copy(m, o.meta)
m[k] = v
}
metadataMu.Unlock() metadataMu.Unlock()
return m, nil return m, nil

View File

@@ -287,13 +287,13 @@ type StartLargeFileRequest struct {
// StartLargeFileResponse is the response to StartLargeFileRequest // StartLargeFileResponse is the response to StartLargeFileRequest
type StartLargeFileResponse struct { type StartLargeFileResponse struct {
ID string `json:"fileId"` // The unique identifier for this version of this file. Used with b2_get_file_info, b2_download_file_by_id, and b2_delete_file_version. ID string `json:"fileId"` // The unique identifier for this version of this file. Used with b2_get_file_info, b2_download_file_by_id, and b2_delete_file_version.
Name string `json:"fileName"` // The name of this file, which can be used with b2_download_file_by_name. Name string `json:"fileName"` // The name of this file, which can be used with b2_download_file_by_name.
AccountID string `json:"accountId"` // The identifier for the account. AccountID string `json:"accountId"` // The identifier for the account.
BucketID string `json:"bucketId"` // The unique ID of the bucket. BucketID string `json:"bucketId"` // The unique ID of the bucket.
ContentType string `json:"contentType"` // The MIME type of the file. ContentType string `json:"contentType"` // The MIME type of the file.
Info map[string]string `json:"fileInfo"` // The custom information that was uploaded with the file. This is a JSON object, holding the name/value pairs that were uploaded with the file. Info map[string]string `json:"fileInfo"` // The custom information that was uploaded with the file. This is a JSON object, holding the name/value pairs that were uploaded with the file.
UploadTimestamp Timestamp `json:"uploadTimestamp,omitempty"` // This is a UTC time when this file was uploaded. UploadTimestamp Timestamp `json:"uploadTimestamp"` // This is a UTC time when this file was uploaded.
} }
// GetUploadPartURLRequest is passed to b2_get_upload_part_url // GetUploadPartURLRequest is passed to b2_get_upload_part_url

View File

@@ -173,6 +173,7 @@ type MultiPartCreateRequest struct {
Extension string `json:"extension"` Extension string `json:"extension"`
ParentID json.Number `json:"parent_id"` ParentID json.Number `json:"parent_id"`
RelativePath string `json:"relativePath"` RelativePath string `json:"relativePath"`
WorkspaceID string `json:"workspaceId,omitempty"`
} }
// MultiPartCreateResponse is returned by POST /s3/multipart/create // MultiPartCreateResponse is returned by POST /s3/multipart/create
@@ -235,3 +236,11 @@ type MultiPartAbort struct {
UploadID string `json:"uploadId"` UploadID string `json:"uploadId"`
Key string `json:"key"` Key string `json:"key"`
} }
// SpaceUsageResponse is returned by GET /user/space-usage
type SpaceUsageResponse struct {
Used int64 `json:"used"`
Available int64 `json:"available"`
Status string `json:"status"`
SEO any `json:"seo"`
}

View File

@@ -476,8 +476,12 @@ func (f *Fs) createDir(ctx context.Context, pathID, leaf string, modTime time.Ti
var resp *http.Response var resp *http.Response
var result api.CreateFolderResponse var result api.CreateFolderResponse
opts := rest.Opts{ opts := rest.Opts{
Method: "POST", Method: "POST",
Path: "/folders", Path: "/folders",
Parameters: url.Values{},
}
if f.opt.WorkspaceID != "" {
opts.Parameters.Set("workspaceId", f.opt.WorkspaceID)
} }
mkdir := api.CreateFolderRequest{ mkdir := api.CreateFolderRequest{
Name: f.opt.Enc.FromStandardName(leaf), Name: f.opt.Enc.FromStandardName(leaf),
@@ -779,8 +783,12 @@ func (f *Fs) patch(ctx context.Context, id, attribute string, value string) (ite
} }
var result api.UpdateItemResponse var result api.UpdateItemResponse
opts := rest.Opts{ opts := rest.Opts{
Method: "PUT", Method: "PUT",
Path: "/file-entries/" + id, Path: "/file-entries/" + id,
Parameters: url.Values{},
}
if f.opt.WorkspaceID != "" {
opts.Parameters.Set("workspaceId", f.opt.WorkspaceID)
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
resp, err = f.srv.CallJSON(ctx, &opts, &request, &result) resp, err = f.srv.CallJSON(ctx, &opts, &request, &result)
@@ -807,8 +815,12 @@ func (f *Fs) move(ctx context.Context, id, newDirID string) (err error) {
} }
var result api.MoveResponse var result api.MoveResponse
opts := rest.Opts{ opts := rest.Opts{
Method: "POST", Method: "POST",
Path: "/file-entries/move", Path: "/file-entries/move",
Parameters: url.Values{},
}
if f.opt.WorkspaceID != "" {
opts.Parameters.Set("workspaceId", f.opt.WorkspaceID)
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
resp, err = f.srv.CallJSON(ctx, &opts, &request, &result) resp, err = f.srv.CallJSON(ctx, &opts, &request, &result)
@@ -945,8 +957,12 @@ func (f *Fs) copy(ctx context.Context, id, newDirID string) (item *api.Item, err
} }
var result api.CopyResponse var result api.CopyResponse
opts := rest.Opts{ opts := rest.Opts{
Method: "POST", Method: "POST",
Path: "/file-entries/duplicate", Path: "/file-entries/duplicate",
Parameters: url.Values{},
}
if f.opt.WorkspaceID != "" {
opts.Parameters.Set("workspaceId", f.opt.WorkspaceID)
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
resp, err = f.srv.CallJSON(ctx, &opts, &request, &result) resp, err = f.srv.CallJSON(ctx, &opts, &request, &result)
@@ -1114,6 +1130,7 @@ func (f *Fs) OpenChunkWriter(ctx context.Context, remote string, src fs.ObjectIn
Extension: strings.TrimPrefix(path.Ext(leaf), `.`), Extension: strings.TrimPrefix(path.Ext(leaf), `.`),
ParentID: json.Number(directoryID), ParentID: json.Number(directoryID),
RelativePath: f.opt.Enc.FromStandardPath(path.Join(f.root, remote)), RelativePath: f.opt.Enc.FromStandardPath(path.Join(f.root, remote)),
WorkspaceID: f.opt.WorkspaceID,
} }
var resp api.MultiPartCreateResponse var resp api.MultiPartCreateResponse
@@ -1344,6 +1361,37 @@ func (s *drimeChunkWriter) Abort(ctx context.Context) error {
return nil return nil
} }
// About gets quota information
func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
opts := rest.Opts{
Method: "GET",
Path: "/user/space-usage",
Parameters: url.Values{},
}
if f.opt.WorkspaceID != "" {
opts.Parameters.Set("workspaceId", f.opt.WorkspaceID)
}
var resp *http.Response
var result api.SpaceUsageResponse
var err error
err = f.pacer.Call(func() (bool, error) {
resp, err = f.srv.CallJSON(ctx, &opts, nil, &result)
return shouldRetry(ctx, resp, err)
})
if err != nil {
return nil, fmt.Errorf("failed to get Drime Quota: %w", err)
}
usage := &fs.Usage{
Total: fs.NewUsageValue(result.Available),
Used: fs.NewUsageValue(result.Used),
Free: fs.NewUsageValue(result.Available - result.Used),
}
return usage, nil
}
// ------------------------------------------------------------ // ------------------------------------------------------------
// Fs returns the parent Fs // Fs returns the parent Fs
@@ -1509,6 +1557,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
MultipartParams: url.Values{ MultipartParams: url.Values{
"parentId": {directoryID}, "parentId": {directoryID},
"relativePath": {encodedLeaf}, "relativePath": {encodedLeaf},
"workspaceId": {o.fs.opt.WorkspaceID},
}, },
MultipartContentName: "file", MultipartContentName: "file",
MultipartFileName: encodedLeaf, MultipartFileName: encodedLeaf,
@@ -1555,6 +1604,7 @@ var (
_ fs.Mover = (*Fs)(nil) _ fs.Mover = (*Fs)(nil)
_ fs.DirMover = (*Fs)(nil) _ fs.DirMover = (*Fs)(nil)
_ fs.DirCacheFlusher = (*Fs)(nil) _ fs.DirCacheFlusher = (*Fs)(nil)
_ fs.Abouter = (*Fs)(nil)
_ fs.OpenChunkWriter = (*Fs)(nil) _ fs.OpenChunkWriter = (*Fs)(nil)
_ fs.Object = (*Object)(nil) _ fs.Object = (*Object)(nil)
_ fs.IDer = (*Object)(nil) _ fs.IDer = (*Object)(nil)

View File

@@ -3,6 +3,19 @@ package api
import "encoding/json" import "encoding/json"
// MultipartInitResponse represents the response from multipart/init.
type MultipartInitResponse struct {
Status int `json:"status"`
Msg string `json:"msg"`
Result struct {
UploadID string `json:"upload_id"`
SessID string `json:"sess_id"`
Server string `json:"server"`
FolderID int64 `json:"folder_id"`
ObjectPath string `json:"object_path"`
} `json:"result"`
}
// CreateFolderResponse represents the response for creating a folder. // CreateFolderResponse represents the response for creating a folder.
type CreateFolderResponse struct { type CreateFolderResponse struct {
Status int `json:"status"` Status int `json:"status"`

View File

@@ -21,6 +21,11 @@ import (
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
) )
const (
defaultUploadCutoff = fs.SizeSuffix(500 * 1024 * 1024)
defaultChunkSize = fs.SizeSuffix(64 * 1024 * 1024)
)
// Register the backend with Rclone // Register the backend with Rclone
func init() { func init() {
fs.Register(&fs.RegInfo{ fs.Register(&fs.RegInfo{
@@ -33,6 +38,17 @@ func init() {
Required: true, Required: true,
Sensitive: true, Sensitive: true,
}, },
{
Name: "upload_cutoff",
Help: "Cutoff for switching to chunked upload. Any files larger than this will be uploaded in chunks of chunk_size.",
Default: defaultUploadCutoff,
Advanced: true,
}, {
Name: "chunk_size",
Help: "Chunk size to use for uploading. Used for multipart uploads.",
Default: defaultChunkSize,
Advanced: true,
},
{ {
Name: config.ConfigEncoding, Name: config.ConfigEncoding,
Help: config.ConfigEncodingHelp, Help: config.ConfigEncodingHelp,
@@ -72,8 +88,10 @@ func init() {
// Options defines the configuration for the FileLu backend // Options defines the configuration for the FileLu backend
type Options struct { type Options struct {
Key string `config:"key"` Key string `config:"key"`
Enc encoder.MultiEncoder `config:"encoding"` Enc encoder.MultiEncoder `config:"encoding"`
UploadCutoff fs.SizeSuffix `config:"upload_cutoff"`
ChunkSize fs.SizeSuffix `config:"chunk_size"`
} }
// Fs represents the FileLu file system // Fs represents the FileLu file system
@@ -189,7 +207,6 @@ func (f *Fs) Purge(ctx context.Context, dir string) error {
return f.deleteFolder(ctx, fullPath) return f.deleteFolder(ctx, fullPath)
} }
// List returns a list of files and folders
// List returns a list of files and folders for the given directory // List returns a list of files and folders for the given directory
func (f *Fs) List(ctx context.Context, dir string) (fs.DirEntries, error) { func (f *Fs) List(ctx context.Context, dir string) (fs.DirEntries, error) {
// Compose full path for API call // Compose full path for API call
@@ -250,23 +267,11 @@ func (f *Fs) List(ctx context.Context, dir string) (fs.DirEntries, error) {
// Put uploads a file directly to the destination folder in the FileLu storage system. // Put uploads a file directly to the destination folder in the FileLu storage system.
func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
if src.Size() == 0 { o := &Object{
return nil, fs.ErrorCantUploadEmptyFiles fs: f,
remote: src.Remote(),
} }
return o, o.Update(ctx, in, src, options...)
err := f.uploadFile(ctx, in, src.Remote())
if err != nil {
return nil, err
}
newObject := &Object{
fs: f,
remote: src.Remote(),
size: src.Size(),
modTime: src.ModTime(ctx),
}
fs.Infof(f, "Put: Successfully uploaded new file %q", src.Remote())
return newObject, nil
} }
// Move moves the file to the specified location // Move moves the file to the specified location

View File

@@ -1,9 +1,7 @@
package filelu package filelu
import ( import (
"bytes"
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -16,40 +14,82 @@ import (
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
) )
// multipartInit starts a new multipart upload and returns server details.
func (f *Fs) multipartInit(ctx context.Context, folderPath, filename string) (*api.MultipartInitResponse, error) {
opts := rest.Opts{
Method: "GET",
Path: "/multipart/init",
Parameters: url.Values{
"key": {f.opt.Key},
"filename": {filename},
"folder_path": {folderPath},
},
}
var result api.MultipartInitResponse
err := f.pacer.Call(func() (bool, error) {
_, err := f.srv.CallJSON(ctx, &opts, nil, &result)
return fserrors.ShouldRetry(err), err
})
if err != nil {
return nil, err
}
if result.Status != 200 {
return nil, fmt.Errorf("multipart init error: %s", result.Msg)
}
return &result, nil
}
// completeMultipart finalizes the multipart upload on the file server.
func (f *Fs) completeMultipart(ctx context.Context, server string, uploadID string, sessID string, objectPath string) error {
req, err := http.NewRequestWithContext(ctx, "POST", server, nil)
if err != nil {
return err
}
req.Header.Set("X-RC-Upload-Id", uploadID)
req.Header.Set("X-Sess-ID", sessID)
req.Header.Set("X-Object-Path", objectPath)
resp, err := f.client.Do(req)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != 202 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("completeMultipart failed %d: %s", resp.StatusCode, string(body))
}
return nil
}
// createFolder creates a folder at the specified path. // createFolder creates a folder at the specified path.
func (f *Fs) createFolder(ctx context.Context, dirPath string) (*api.CreateFolderResponse, error) { func (f *Fs) createFolder(ctx context.Context, dirPath string) (*api.CreateFolderResponse, error) {
encodedDir := f.fromStandardPath(dirPath) encodedDir := f.fromStandardPath(dirPath)
apiURL := fmt.Sprintf("%s/folder/create?folder_path=%s&key=%s",
f.endpoint,
url.QueryEscape(encodedDir),
url.QueryEscape(f.opt.Key), // assuming f.opt.Key is the correct field
)
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) opts := rest.Opts{
if err != nil { Method: "GET",
return nil, fmt.Errorf("failed to create request: %w", err) Path: "/folder/create",
Parameters: url.Values{
"folder_path": {encodedDir},
"key": {f.opt.Key},
},
} }
var resp *http.Response var result api.CreateFolderResponse
result := api.CreateFolderResponse{}
err = f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
var innerErr error _, err := f.srv.CallJSON(ctx, &opts, nil, &result)
resp, innerErr = f.client.Do(req) return fserrors.ShouldRetry(err), err
return fserrors.ShouldRetry(innerErr), innerErr
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("request failed: %w", err) return nil, fmt.Errorf("request failed: %w", err)
} }
defer func() {
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return nil, fmt.Errorf("error decoding response: %w", err)
}
if result.Status != 200 { if result.Status != 200 {
return nil, fmt.Errorf("error: %s", result.Msg) return nil, fmt.Errorf("error: %s", result.Msg)
} }
@@ -61,44 +101,29 @@ func (f *Fs) createFolder(ctx context.Context, dirPath string) (*api.CreateFolde
// getFolderList List both files and folders in a directory. // getFolderList List both files and folders in a directory.
func (f *Fs) getFolderList(ctx context.Context, path string) (*api.FolderListResponse, error) { func (f *Fs) getFolderList(ctx context.Context, path string) (*api.FolderListResponse, error) {
encodedDir := f.fromStandardPath(path) encodedDir := f.fromStandardPath(path)
apiURL := fmt.Sprintf("%s/folder/list?folder_path=%s&key=%s",
f.endpoint,
url.QueryEscape(encodedDir),
url.QueryEscape(f.opt.Key),
)
var body []byte opts := rest.Opts{
Method: "GET",
Path: "/folder/list",
Parameters: url.Values{
"folder_path": {encodedDir},
"key": {f.opt.Key},
},
}
var response api.FolderListResponse
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) _, err := f.srv.CallJSON(ctx, &opts, nil, &response)
if err != nil {
return false, fmt.Errorf("failed to create request: %w", err)
}
resp, err := f.client.Do(req)
if err != nil { if err != nil {
return shouldRetry(err), fmt.Errorf("failed to list directory: %w", err) return shouldRetry(err), fmt.Errorf("failed to list directory: %w", err)
} }
defer func() { return false, nil
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
body, err = io.ReadAll(resp.Body)
if err != nil {
return false, fmt.Errorf("error reading response body: %w", err)
}
return shouldRetryHTTP(resp.StatusCode), nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
var response api.FolderListResponse
if err := json.NewDecoder(bytes.NewReader(body)).Decode(&response); err != nil {
return nil, fmt.Errorf("error decoding response: %w", err)
}
if response.Status != 200 { if response.Status != 200 {
if strings.Contains(response.Msg, "Folder not found") { if strings.Contains(response.Msg, "Folder not found") {
return nil, fs.ErrorDirNotFound return nil, fs.ErrorDirNotFound
@@ -115,42 +140,28 @@ func (f *Fs) getFolderList(ctx context.Context, path string) (*api.FolderListRes
} }
return &response, nil return &response, nil
} }
// deleteFolder deletes a folder at the specified path. // deleteFolder deletes a folder at the specified path.
func (f *Fs) deleteFolder(ctx context.Context, fullPath string) error { func (f *Fs) deleteFolder(ctx context.Context, fullPath string) error {
fullPath = f.fromStandardPath(fullPath) fullPath = f.fromStandardPath(fullPath)
deleteURL := fmt.Sprintf("%s/folder/delete?folder_path=%s&key=%s",
f.endpoint, opts := rest.Opts{
url.QueryEscape(fullPath), Method: "GET",
url.QueryEscape(f.opt.Key), Path: "/folder/delete",
) Parameters: url.Values{
"folder_path": {fullPath},
"key": {f.opt.Key},
},
}
delResp := api.DeleteFolderResponse{} delResp := api.DeleteFolderResponse{}
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", deleteURL, nil) _, err := f.srv.CallJSON(ctx, &opts, nil, &delResp)
if err != nil {
return false, err
}
resp, err := f.client.Do(req)
if err != nil { if err != nil {
return fserrors.ShouldRetry(err), err return fserrors.ShouldRetry(err), err
} }
defer func() {
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
if err := json.Unmarshal(body, &delResp); err != nil {
return false, fmt.Errorf("error decoding delete response: %w", err)
}
if delResp.Status != 200 { if delResp.Status != 200 {
return false, fmt.Errorf("delete error: %s", delResp.Msg) return false, fmt.Errorf("delete error: %s", delResp.Msg)
} }
@@ -167,38 +178,27 @@ func (f *Fs) deleteFolder(ctx context.Context, fullPath string) error {
// getDirectLink of file from FileLu to download. // getDirectLink of file from FileLu to download.
func (f *Fs) getDirectLink(ctx context.Context, filePath string) (string, int64, error) { func (f *Fs) getDirectLink(ctx context.Context, filePath string) (string, int64, error) {
filePath = f.fromStandardPath(filePath) filePath = f.fromStandardPath(filePath)
apiURL := fmt.Sprintf("%s/file/direct_link?file_path=%s&key=%s",
f.endpoint, opts := rest.Opts{
url.QueryEscape(filePath), Method: "GET",
url.QueryEscape(f.opt.Key), Path: "/file/direct_link",
) Parameters: url.Values{
"file_path": {filePath},
"key": {f.opt.Key},
},
}
result := api.FileDirectLinkResponse{} result := api.FileDirectLinkResponse{}
err := f.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
if err != nil {
return false, fmt.Errorf("failed to create request: %w", err)
}
resp, err := f.client.Do(req) err := f.pacer.Call(func() (bool, error) {
_, err := f.srv.CallJSON(ctx, &opts, nil, &result)
if err != nil { if err != nil {
return shouldRetry(err), fmt.Errorf("failed to fetch direct link: %w", err) return shouldRetry(err), fmt.Errorf("failed to fetch direct link: %w", err)
} }
defer func() {
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, fmt.Errorf("error decoding response: %w", err)
}
if result.Status != 200 { if result.Status != 200 {
return false, fmt.Errorf("API error: %s", result.Msg) return false, fmt.Errorf("API error: %s", result.Msg)
} }
return false, nil
return shouldRetryHTTP(resp.StatusCode), nil
}) })
if err != nil { if err != nil {
return "", 0, err return "", 0, err
@@ -210,39 +210,31 @@ func (f *Fs) getDirectLink(ctx context.Context, filePath string) (string, int64,
// deleteFile deletes a file based on filePath // deleteFile deletes a file based on filePath
func (f *Fs) deleteFile(ctx context.Context, filePath string) error { func (f *Fs) deleteFile(ctx context.Context, filePath string) error {
filePath = f.fromStandardPath(filePath) filePath = f.fromStandardPath(filePath)
apiURL := fmt.Sprintf("%s/file/remove?file_path=%s&key=%s",
f.endpoint, opts := rest.Opts{
url.QueryEscape(filePath), Method: "GET",
url.QueryEscape(f.opt.Key), Path: "/file/remove",
) Parameters: url.Values{
"file_path": {filePath},
"key": {f.opt.Key},
},
}
result := api.DeleteFileResponse{} result := api.DeleteFileResponse{}
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) _, err := f.srv.CallJSON(ctx, &opts, nil, &result)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to create request: %w", err) return shouldRetry(err), fmt.Errorf("failed to delete file: %w", err)
}
resp, err := f.client.Do(req)
if err != nil {
return shouldRetry(err), fmt.Errorf("failed to fetch direct link: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, fmt.Errorf("error decoding response: %w", err)
} }
if result.Status != 200 { if result.Status != 200 {
return false, fmt.Errorf("API error: %s", result.Msg) return false, fmt.Errorf("API error: %s", result.Msg)
} }
return shouldRetryHTTP(resp.StatusCode), nil return false, nil
}) })
return err return err
} }
@@ -275,46 +267,28 @@ func (f *Fs) getAccountInfo(ctx context.Context) (*api.AccountInfoResponse, erro
// getFileInfo retrieves file information based on file code // getFileInfo retrieves file information based on file code
func (f *Fs) getFileInfo(ctx context.Context, fileCode string) (*api.FileInfoResponse, error) { func (f *Fs) getFileInfo(ctx context.Context, fileCode string) (*api.FileInfoResponse, error) {
u, _ := url.Parse(f.endpoint + "/file/info2")
q := u.Query()
q.Set("file_code", fileCode) // raw path — Go handles escaping properly here
q.Set("key", f.opt.Key)
u.RawQuery = q.Encode()
apiURL := f.endpoint + "/file/info2?" + u.RawQuery opts := rest.Opts{
Method: "GET",
Path: "/file/info2",
Parameters: url.Values{
"file_code": {fileCode},
"key": {f.opt.Key},
},
}
result := api.FileInfoResponse{}
var body []byte
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) _, err := f.srv.CallJSON(ctx, &opts, nil, &result)
if err != nil {
return false, fmt.Errorf("failed to create request: %w", err)
}
resp, err := f.client.Do(req)
if err != nil { if err != nil {
return shouldRetry(err), fmt.Errorf("failed to fetch file info: %w", err) return shouldRetry(err), fmt.Errorf("failed to fetch file info: %w", err)
} }
defer func() { return false, nil
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
body, err = io.ReadAll(resp.Body)
if err != nil {
return false, fmt.Errorf("error reading response body: %w", err)
}
return shouldRetryHTTP(resp.StatusCode), nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := api.FileInfoResponse{}
if err := json.NewDecoder(bytes.NewReader(body)).Decode(&result); err != nil {
return nil, fmt.Errorf("error decoding response: %w", err)
}
if result.Status != 200 || len(result.Result) == 0 { if result.Status != 200 || len(result.Result) == 0 {
return nil, fs.ErrorObjectNotFound return nil, fs.ErrorObjectNotFound

View File

@@ -1,6 +1,7 @@
package filelu package filelu
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -13,8 +14,108 @@ import (
"strings" "strings"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/lib/rest"
) )
// multipartUpload uploads a file in fixed-size chunks using the multipart API.
func (f *Fs) multipartUpload(ctx context.Context, in io.Reader, remote string) error {
dir := path.Dir(remote)
if dir == "." {
dir = ""
}
if dir != "" {
_ = f.Mkdir(ctx, dir)
}
folder := strings.Trim(dir, "/")
if folder != "" {
folder = "/" + folder
}
file := path.Base(remote)
initResp, err := f.multipartInit(ctx, folder, file)
if err != nil {
return fmt.Errorf("multipart init failed: %w", err)
}
uploadID := initResp.Result.UploadID
sessID := initResp.Result.SessID
server := initResp.Result.Server
objectPath := initResp.Result.ObjectPath
chunkSize := int(f.opt.ChunkSize)
buf := make([]byte, 0, chunkSize)
tmp := make([]byte, 1024*1024)
partNo := 1
for {
n, errRead := in.Read(tmp)
if n > 0 {
buf = append(buf, tmp[:n]...)
// If buffer reached chunkSize, upload a full part
if len(buf) >= chunkSize {
err = f.uploadPart(ctx, server, uploadID, sessID, objectPath, partNo, bytes.NewReader(buf))
if err != nil {
return fmt.Errorf("upload part %d failed: %w", partNo, err)
}
partNo++
buf = buf[:0]
}
}
if errRead == io.EOF {
break
}
if errRead != nil {
return fmt.Errorf("read failed: %w", errRead)
}
}
if len(buf) > 0 {
err = f.uploadPart(ctx, server, uploadID, sessID, objectPath, partNo, bytes.NewReader(buf))
if err != nil {
return fmt.Errorf("upload part %d failed: %w", partNo, err)
}
}
err = f.completeMultipart(ctx, server, uploadID, sessID, objectPath)
if err != nil {
return fmt.Errorf("complete multipart failed: %w", err)
}
return nil
}
// uploadPart sends a single multipart chunk to the upload server.
func (f *Fs) uploadPart(ctx context.Context, server, uploadID, sessID, objectPath string, partNo int, r io.Reader) error {
url := fmt.Sprintf("%s?partNumber=%d&uploadId=%s", server, partNo, uploadID)
req, err := http.NewRequestWithContext(ctx, "PUT", url, r)
if err != nil {
return err
}
req.Header.Set("X-RC-Upload-Id", uploadID)
req.Header.Set("X-RC-Part-No", fmt.Sprintf("%d", partNo))
req.Header.Set("X-Sess-ID", sessID)
req.Header.Set("X-Object-Path", objectPath)
resp, err := f.client.Do(req)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != 200 {
return fmt.Errorf("uploadPart failed: %s", resp.Status)
}
return nil
}
// uploadFile uploads a file to FileLu // uploadFile uploads a file to FileLu
func (f *Fs) uploadFile(ctx context.Context, fileContent io.Reader, fileFullPath string) error { func (f *Fs) uploadFile(ctx context.Context, fileContent io.Reader, fileFullPath string) error {
directory := path.Dir(fileFullPath) directory := path.Dir(fileFullPath)
@@ -68,9 +169,15 @@ func (f *Fs) uploadFile(ctx context.Context, fileContent io.Reader, fileFullPath
return nil return nil
} }
// getUploadServer gets the upload server URL with proper key authentication
func (f *Fs) getUploadServer(ctx context.Context) (string, string, error) { func (f *Fs) getUploadServer(ctx context.Context) (string, string, error) {
apiURL := fmt.Sprintf("%s/upload/server?key=%s", f.endpoint, url.QueryEscape(f.opt.Key))
opts := rest.Opts{
Method: "GET",
Path: "/upload/server",
Parameters: url.Values{
"key": {f.opt.Key},
},
}
var result struct { var result struct {
Status int `json:"status"` Status int `json:"status"`
@@ -80,36 +187,21 @@ func (f *Fs) getUploadServer(ctx context.Context) (string, string, error) {
} }
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) _, err := f.srv.CallJSON(ctx, &opts, nil, &result)
if err != nil {
return false, fmt.Errorf("failed to create request: %w", err)
}
resp, err := f.client.Do(req)
if err != nil { if err != nil {
return shouldRetry(err), fmt.Errorf("failed to get upload server: %w", err) return shouldRetry(err), fmt.Errorf("failed to get upload server: %w", err)
} }
defer func() { return false, nil
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, fmt.Errorf("error decoding response: %w", err)
}
if result.Status != 200 {
return false, fmt.Errorf("API error: %s", result.Msg)
}
return shouldRetryHTTP(resp.StatusCode), nil
}) })
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
if result.Status != 200 {
return "", "", fmt.Errorf("API error: %s", result.Msg)
}
return result.Result, result.SessID, nil return result.Result, result.SessID, nil
} }

View File

@@ -1,9 +1,7 @@
package filelu package filelu
import ( import (
"bytes"
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -16,6 +14,7 @@ import (
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/hash" "github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/lib/rest"
) )
// Object describes a FileLu object // Object describes a FileLu object
@@ -88,6 +87,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadClo
} }
} }
// Wrap the response body to handle offset and count
var reader io.ReadCloser var reader io.ReadCloser
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", directLink, nil) req, err := http.NewRequestWithContext(ctx, "GET", directLink, nil)
@@ -109,22 +109,25 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadClo
return false, fmt.Errorf("failed to download file: HTTP %d", resp.StatusCode) return false, fmt.Errorf("failed to download file: HTTP %d", resp.StatusCode)
} }
// Wrap the response body to handle offset and count if offset > 0 {
currentContents, err := io.ReadAll(resp.Body) _, err = io.CopyN(io.Discard, resp.Body, offset)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to read response body: %w", err) _ = resp.Body.Close()
return false, fmt.Errorf("failed to skip offset: %w", err)
}
} }
if offset > 0 { if count > 0 {
if offset > int64(len(currentContents)) { reader = struct {
return false, fmt.Errorf("offset %d exceeds file size %d", offset, len(currentContents)) io.Reader
io.Closer
}{
Reader: io.LimitReader(resp.Body, count),
Closer: resp.Body,
} }
currentContents = currentContents[offset:] } else {
reader = resp.Body
} }
if count > 0 && count < int64(len(currentContents)) {
currentContents = currentContents[:count]
}
reader = io.NopCloser(bytes.NewReader(currentContents))
return false, nil return false, nil
}) })
@@ -137,15 +140,23 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadClo
// Update updates the object with new data // Update updates the object with new data
func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
if src.Size() <= 0 { size := src.Size()
return fs.ErrorCantUploadEmptyFiles
if size <= int64(o.fs.opt.UploadCutoff) {
err := o.fs.uploadFile(ctx, in, o.remote)
if err != nil {
return err
}
} else {
fullPath := path.Join(o.fs.root, o.remote)
err := o.fs.multipartUpload(ctx, in, fullPath)
if err != nil {
return fmt.Errorf("failed to upload file: %w", err)
}
} }
err := o.fs.uploadFile(ctx, in, o.remote) o.size = size
if err != nil { o.modTime = src.ModTime(ctx)
return fmt.Errorf("failed to upload file: %w", err)
}
o.size = src.Size()
return nil return nil
} }
@@ -183,8 +194,14 @@ func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
return "", fmt.Errorf("no valid file code found in the remote path") return "", fmt.Errorf("no valid file code found in the remote path")
} }
apiURL := fmt.Sprintf("%s/file/info?file_code=%s&key=%s", opts := rest.Opts{
o.fs.endpoint, url.QueryEscape(fileCode), url.QueryEscape(o.fs.opt.Key)) Method: "GET",
Path: "/file/info",
Parameters: url.Values{
"file_code": {fileCode},
"key": {o.fs.opt.Key},
},
}
var result struct { var result struct {
Status int `json:"status"` Status int `json:"status"`
@@ -193,29 +210,18 @@ func (o *Object) Hash(ctx context.Context, t hash.Type) (string, error) {
Hash string `json:"hash"` Hash string `json:"hash"`
} `json:"result"` } `json:"result"`
} }
err := o.fs.pacer.Call(func() (bool, error) { err := o.fs.pacer.Call(func() (bool, error) {
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) _, err := o.fs.srv.CallJSON(ctx, &opts, nil, &result)
if err != nil {
return false, err
}
resp, err := o.fs.client.Do(req)
if err != nil { if err != nil {
return shouldRetry(err), err return shouldRetry(err), err
} }
defer func() { return false, nil
if err := resp.Body.Close(); err != nil {
fs.Logf(nil, "Failed to close response body: %v", err)
}
}()
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, err
}
return shouldRetryHTTP(resp.StatusCode), nil
}) })
if err != nil { if err != nil {
return "", err return "", err
} }
if result.Status != 200 || len(result.Result) == 0 { if result.Status != 200 || len(result.Result) == 0 {
return "", fmt.Errorf("error: unable to fetch hash: %s", result.Msg) return "", fmt.Errorf("error: unable to fetch hash: %s", result.Msg)
} }

View File

@@ -355,19 +355,19 @@ type chunkWriter struct {
chunkSize int64 chunkSize int64
chunksLock sync.Mutex chunksLock sync.Mutex
knownChunks map[int][]byte // known chunks to be hashed knownChunks map[int64][]byte // known chunks to be hashed
nextChunkToHash int nextChunkToHash int64
sizeLock sync.Mutex sizeLock sync.Mutex
size int64 size int64
} }
func (cw *chunkWriter) WriteChunk(ctx context.Context, chunkNumber int, reader io.ReadSeeker) (bytesWritten int64, err error) { func (cw *chunkWriter) WriteChunk(ctx context.Context, chunkNumber int, reader io.ReadSeeker) (bytesWritten int64, err error) {
realChunkNumber := int(int64(chunkNumber) * (cw.chunkSize) / sdk.ChunkSize) realChunkNumber := int64(chunkNumber) * (cw.chunkSize) / sdk.ChunkSize
chunk := make([]byte, sdk.ChunkSize, sdk.ChunkSize+cw.EncryptionKey.Cipher.Overhead()) chunk := make([]byte, sdk.ChunkSize, sdk.ChunkSize+cw.EncryptionKey.Cipher.Overhead())
totalWritten := int64(0) totalWritten := int64(0)
for sliceStart := 0; sliceStart < int(cw.chunkSize); sliceStart += sdk.ChunkSize { for sliceStart := int64(0); sliceStart < cw.chunkSize; sliceStart += sdk.ChunkSize {
chunk = chunk[:sdk.ChunkSize] chunk = chunk[:sdk.ChunkSize]
chunkRead := 0 chunkRead := 0
for { for {
@@ -415,13 +415,13 @@ func (cw *chunkWriter) WriteChunk(ctx context.Context, chunkNumber int, reader i
return totalWritten, err return totalWritten, err
} }
resp, err := cw.filen.UploadChunk(ctx, &cw.FileUpload, realChunkNumber, chunkReadSlice) resp, err := cw.filen.UploadChunk(ctx, &cw.FileUpload, realChunkNumber, chunkReadSlice)
if err != nil {
return totalWritten, err
}
select { // only care about getting this once select { // only care about getting this once
case cw.bucketAndRegion <- *resp: case cw.bucketAndRegion <- *resp:
default: default:
} }
if err != nil {
return totalWritten, err
}
totalWritten += int64(len(chunkReadSlice)) totalWritten += int64(len(chunkReadSlice))
realChunkNumber++ realChunkNumber++
} }
@@ -496,7 +496,7 @@ func (f *Fs) OpenChunkWriter(ctx context.Context, remote string, src fs.ObjectIn
filen: f.filen, filen: f.filen,
chunkSize: chunkSize, chunkSize: chunkSize,
bucketAndRegion: make(chan client.V3UploadResponse, 1), bucketAndRegion: make(chan client.V3UploadResponse, 1),
knownChunks: make(map[int][]byte), knownChunks: make(map[int64][]byte),
nextChunkToHash: 0, nextChunkToHash: 0,
size: 0, size: 0,
}, nil }, nil
@@ -1122,8 +1122,8 @@ func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
return nil, err return nil, err
} }
total := int64(userInfo.MaxStorage) total := userInfo.MaxStorage
used := int64(userInfo.UsedStorage) used := userInfo.UsedStorage
free := total - used free := total - used
return &fs.Usage{ return &fs.Usage{
Total: &total, Total: &total,

View File

@@ -116,8 +116,8 @@ type Date struct {
type DateFilter struct { type DateFilter struct {
Dates []Date `json:"dates,omitempty"` Dates []Date `json:"dates,omitempty"`
Ranges []struct { Ranges []struct {
StartDate Date `json:"startDate,omitempty"` StartDate Date `json:"startDate"`
EndDate Date `json:"endDate,omitempty"` EndDate Date `json:"endDate"`
} `json:"ranges,omitempty"` } `json:"ranges,omitempty"`
} }

View File

@@ -583,9 +583,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
entriesMu.Unlock() entriesMu.Unlock()
} }
for range checkers { for range checkers {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
for remote := range in { for remote := range in {
file := &Object{ file := &Object{
fs: f, fs: f,
@@ -601,7 +599,7 @@ func (f *Fs) List(ctx context.Context, dir string) (entries fs.DirEntries, err e
fs.Debugf(remote, "skipping because of error: %v", err) fs.Debugf(remote, "skipping because of error: %v", err)
} }
} }
}() })
} }
for _, name := range names { for _, name := range names {
isDir := name[len(name)-1] == '/' isDir := name[len(name)-1] == '/'

View File

@@ -599,7 +599,7 @@ type UpdateFileInfo struct {
Signature string `json:"signature,omitempty"` Signature string `json:"signature,omitempty"`
Size int64 `json:"size,omitempty"` Size int64 `json:"size,omitempty"`
WrappingKey string `json:"wrapping_key,omitempty"` WrappingKey string `json:"wrapping_key,omitempty"`
} `json:"data,omitempty"` } `json:"data"`
DocumentID string `json:"document_id"` DocumentID string `json:"document_id"`
FileFlags FileFlags `json:"file_flags"` FileFlags FileFlags `json:"file_flags"`
Mtime int64 `json:"mtime"` Mtime int64 `json:"mtime"`
@@ -849,10 +849,10 @@ type DriveItem struct {
NumberOfItems int64 `json:"numberOfItems"` NumberOfItems int64 `json:"numberOfItems"`
Status string `json:"status"` Status string `json:"status"`
Extension string `json:"extension,omitempty"` Extension string `json:"extension,omitempty"`
DateModified time.Time `json:"dateModified,omitempty"` DateModified time.Time `json:"dateModified"`
DateChanged time.Time `json:"dateChanged,omitempty"` DateChanged time.Time `json:"dateChanged"`
Size int64 `json:"size,omitempty"` Size int64 `json:"size,omitempty"`
LastOpenTime time.Time `json:"lastOpenTime,omitempty"` LastOpenTime time.Time `json:"lastOpenTime"`
Urls struct { Urls struct {
URLDownload string `json:"url_download"` URLDownload string `json:"url_download"`
} `json:"urls"` } `json:"urls"`

View File

@@ -13,8 +13,12 @@ import (
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
internxtauth "github.com/internxt/rclone-adapter/auth" internxtauth "github.com/internxt/rclone-adapter/auth"
internxtconfig "github.com/internxt/rclone-adapter/config" internxtconfig "github.com/internxt/rclone-adapter/config"
sdkerrors "github.com/internxt/rclone-adapter/errors"
"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/obscure"
"github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/fs/fshttp"
"github.com/rclone/rclone/lib/oauthutil" "github.com/rclone/rclone/lib/oauthutil"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@@ -101,7 +105,6 @@ func jwtToOAuth2Token(jwtString string) (*oauth2.Token, error) {
} }
// computeBasicAuthHeader creates the BasicAuthHeader for bucket operations // computeBasicAuthHeader creates the BasicAuthHeader for bucket operations
// Following the pattern from SDK's auth/access.go:96-102
func computeBasicAuthHeader(bridgeUser, userID string) string { func computeBasicAuthHeader(bridgeUser, userID string) string {
sum := sha256.Sum256([]byte(userID)) sum := sha256.Sum256([]byte(userID))
hexPass := hex.EncodeToString(sum[:]) hexPass := hex.EncodeToString(sum[:])
@@ -144,3 +147,100 @@ func refreshJWTToken(ctx context.Context, name string, m configmap.Mapper) error
fs.Debugf(name, "Token refreshed successfully, new expiry: %v", token.Expiry) fs.Debugf(name, "Token refreshed successfully, new expiry: %v", token.Expiry)
return nil return nil
} }
// reLogin performs a full re-login using stored email+password credentials.
// Returns the AccessResponse on success, or an error if 2FA is required or login fails.
func (f *Fs) reLogin(ctx context.Context) (*internxtauth.AccessResponse, error) {
password, err := obscure.Reveal(f.opt.Pass)
if err != nil {
return nil, fmt.Errorf("couldn't decrypt password: %w", err)
}
cfg := internxtconfig.NewDefaultToken("")
cfg.HTTPClient = fshttp.NewClient(ctx)
loginResp, err := internxtauth.Login(ctx, cfg, f.opt.Email)
if err != nil {
return nil, fmt.Errorf("re-login check failed: %w", err)
}
if loginResp.TFA {
return nil, errors.New("account requires 2FA - please run: rclone config reconnect " + f.name + ":")
}
resp, err := internxtauth.DoLogin(ctx, cfg, f.opt.Email, password, "")
if err != nil {
return nil, fmt.Errorf("re-login failed: %w", err)
}
return resp, nil
}
// refreshOrReLogin tries to refresh the JWT token first; if that fails with 401,
// it falls back to a full re-login using stored credentials.
func (f *Fs) refreshOrReLogin(ctx context.Context) error {
refreshErr := refreshJWTToken(ctx, f.name, f.m)
if refreshErr == nil {
newToken, err := oauthutil.GetToken(f.name, f.m)
if err != nil {
return fmt.Errorf("failed to get refreshed token: %w", err)
}
f.cfg.Token = newToken.AccessToken
f.cfg.BasicAuthHeader = computeBasicAuthHeader(f.bridgeUser, f.userID)
fs.Debugf(f, "Token refresh succeeded")
return nil
}
var httpErr *sdkerrors.HTTPError
if !errors.As(refreshErr, &httpErr) || httpErr.StatusCode() != 401 {
if fserrors.ShouldRetry(refreshErr) {
return refreshErr
}
return refreshErr
}
fs.Debugf(f, "Token refresh returned 401, attempting re-login with stored credentials")
resp, err := f.reLogin(ctx)
if err != nil {
return fmt.Errorf("re-login fallback failed: %w", err)
}
oauthToken, err := jwtToOAuth2Token(resp.NewToken)
if err != nil {
return fmt.Errorf("failed to parse re-login token: %w", err)
}
err = oauthutil.PutToken(f.name, f.m, oauthToken, true)
if err != nil {
return fmt.Errorf("failed to save re-login token: %w", err)
}
f.cfg.Token = oauthToken.AccessToken
f.bridgeUser = resp.User.BridgeUser
f.userID = resp.User.UserID
f.cfg.BasicAuthHeader = computeBasicAuthHeader(f.bridgeUser, f.userID)
f.cfg.Bucket = resp.User.Bucket
f.cfg.RootFolderID = resp.User.RootFolderID
fs.Debugf(f, "Re-login succeeded, new token expiry: %v", oauthToken.Expiry)
return nil
}
// reAuthorize is called after getting 401 from the server.
// It serializes re-auth attempts and uses a circuit-breaker to avoid infinite loops.
func (f *Fs) reAuthorize(ctx context.Context) error {
f.authMu.Lock()
defer f.authMu.Unlock()
if f.authFailed {
return errors.New("re-authorization permanently failed")
}
err := f.refreshOrReLogin(ctx)
if err != nil {
f.authFailed = true
return err
}
return nil
}

View File

@@ -11,6 +11,7 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"time" "time"
"github.com/internxt/rclone-adapter/auth" "github.com/internxt/rclone-adapter/auth"
@@ -41,16 +42,34 @@ const (
decayConstant = 2 // bigger for slower decay, exponential decayConstant = 2 // bigger for slower decay, exponential
) )
// shouldRetry determines if an error should be retried // shouldRetry determines if an error should be retried.
func shouldRetry(ctx context.Context, err error) (bool, error) { // On 401, it attempts to re-authorize before retrying.
// On 429, it honours the server's rate limit retry delay.
func (f *Fs) shouldRetry(ctx context.Context, err error) (bool, error) {
if fserrors.ContextError(ctx, &err) { if fserrors.ContextError(ctx, &err) {
return false, err return false, err
} }
var httpErr *sdkerrors.HTTPError var httpErr *sdkerrors.HTTPError
if errors.As(err, &httpErr) && httpErr.StatusCode() == 401 { if errors.As(err, &httpErr) {
return true, err switch httpErr.StatusCode() {
case 401:
if !f.authFailed {
authErr := f.reAuthorize(ctx)
if authErr != nil {
fs.Debugf(f, "Re-authorization failed: %v", authErr)
return false, err
}
return true, err
}
return false, err
case 429:
delay := httpErr.RetryAfter()
if delay <= 0 {
delay = time.Second
}
return true, pacer.RetryAfterError(err, delay)
}
} }
return fserrors.ShouldRetry(err), err return fserrors.ShouldRetry(err), err
} }
@@ -184,6 +203,7 @@ type Fs struct {
name string name string
root string root string
opt Options opt Options
m configmap.Mapper
dirCache *dircache.DirCache dirCache *dircache.DirCache
cfg *config.Config cfg *config.Config
features *fs.Features features *fs.Features
@@ -191,6 +211,8 @@ type Fs struct {
tokenRenewer *oauthutil.Renew tokenRenewer *oauthutil.Renew
bridgeUser string bridgeUser string
userID string userID string
authMu sync.Mutex
authFailed bool
} }
// Object holds the data for a remote file object // Object holds the data for a remote file object
@@ -263,45 +285,62 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
cfg.SkipHashValidation = opt.SkipHashValidation cfg.SkipHashValidation = opt.SkipHashValidation
cfg.HTTPClient = fshttp.NewClient(ctx) cfg.HTTPClient = fshttp.NewClient(ctx)
userInfo, err := getUserInfo(ctx, &userInfoConfig{Token: cfg.Token})
if err != nil {
return nil, fmt.Errorf("failed to fetch user info: %w", err)
}
cfg.RootFolderID = userInfo.RootFolderID
cfg.Bucket = userInfo.Bucket
cfg.BasicAuthHeader = computeBasicAuthHeader(userInfo.BridgeUser, userInfo.UserID)
f := &Fs{ f := &Fs{
name: name, name: name,
root: strings.Trim(root, "/"), root: strings.Trim(root, "/"),
opt: *opt, opt: *opt,
cfg: cfg, m: m,
bridgeUser: userInfo.BridgeUser, cfg: cfg,
userID: userInfo.UserID,
} }
f.pacer = fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))) f.pacer = fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant)))
var userInfo *userInfo
const maxRetries = 3
for attempt := 1; attempt <= maxRetries; attempt++ {
userInfo, err = getUserInfo(ctx, &userInfoConfig{Token: f.cfg.Token})
if err == nil {
break
}
var httpErr *sdkerrors.HTTPError
if errors.As(err, &httpErr) && httpErr.StatusCode() == 401 {
fs.Debugf(f, "getUserInfo returned 401, attempting re-auth")
authErr := f.refreshOrReLogin(ctx)
if authErr != nil {
return nil, fmt.Errorf("failed to fetch user info (re-auth failed): %w", err)
}
userInfo, err = getUserInfo(ctx, &userInfoConfig{Token: f.cfg.Token})
if err == nil {
break
}
return nil, fmt.Errorf("failed to fetch user info after re-auth: %w", err)
}
if fserrors.ShouldRetry(err) && attempt < maxRetries {
fs.Debugf(f, "getUserInfo transient error (attempt %d/%d): %v", attempt, maxRetries, err)
time.Sleep(time.Duration(attempt) * time.Second)
continue
}
return nil, fmt.Errorf("failed to fetch user info: %w", err)
}
f.cfg.RootFolderID = userInfo.RootFolderID
f.cfg.Bucket = userInfo.Bucket
f.cfg.BasicAuthHeader = computeBasicAuthHeader(userInfo.BridgeUser, userInfo.UserID)
f.bridgeUser = userInfo.BridgeUser
f.userID = userInfo.UserID
f.features = (&fs.Features{ f.features = (&fs.Features{
CanHaveEmptyDirectories: true, CanHaveEmptyDirectories: true,
}).Fill(ctx, f) }).Fill(ctx, f)
if ts != nil { if ts != nil {
f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error { f.tokenRenewer = oauthutil.NewRenew(f.String(), ts, func() error {
err := refreshJWTToken(ctx, name, m) f.authMu.Lock()
if err != nil { defer f.authMu.Unlock()
return err return f.refreshOrReLogin(ctx)
}
newToken, err := oauthutil.GetToken(name, m)
if err != nil {
return fmt.Errorf("failed to get refreshed token: %w", err)
}
f.cfg.Token = newToken.AccessToken
f.cfg.BasicAuthHeader = computeBasicAuthHeader(f.bridgeUser, f.userID)
return nil
}) })
f.tokenRenewer.Start() f.tokenRenewer.Start()
} }
@@ -312,9 +351,19 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
if err != nil { if err != nil {
// Assume it might be a file // Assume it might be a file
newRoot, remote := dircache.SplitPath(f.root) newRoot, remote := dircache.SplitPath(f.root)
tempF := *f tempF := &Fs{
tempF.dirCache = dircache.New(newRoot, f.cfg.RootFolderID, &tempF) name: f.name,
tempF.root = newRoot root: newRoot,
opt: f.opt,
m: f.m,
cfg: f.cfg,
features: f.features,
pacer: f.pacer,
tokenRenewer: f.tokenRenewer,
bridgeUser: f.bridgeUser,
userID: f.userID,
}
tempF.dirCache = dircache.New(newRoot, f.cfg.RootFolderID, tempF)
err = tempF.dirCache.FindRoot(ctx, false) err = tempF.dirCache.FindRoot(ctx, false)
if err != nil { if err != nil {
@@ -367,7 +416,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
var err error var err error
childFolders, err = folders.ListAllFolders(ctx, f.cfg, id) childFolders, err = folders.ListAllFolders(ctx, f.cfg, id)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return err return err
@@ -380,7 +429,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
var err error var err error
childFiles, err = folders.ListAllFiles(ctx, f.cfg, id) childFiles, err = folders.ListAllFiles(ctx, f.cfg, id)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return err return err
@@ -395,7 +444,7 @@ func (f *Fs) Rmdir(ctx context.Context, dir string) error {
if err != nil && strings.Contains(err.Error(), "404") { if err != nil && strings.Contains(err.Error(), "404") {
return false, fs.ErrorDirNotFound return false, fs.ErrorDirNotFound
} }
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return err return err
@@ -412,7 +461,7 @@ func (f *Fs) FindLeaf(ctx context.Context, pathID, leaf string) (string, bool, e
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
var err error var err error
entries, err = folders.ListAllFolders(ctx, f.cfg, pathID) entries, err = folders.ListAllFolders(ctx, f.cfg, pathID)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return "", false, err return "", false, err
@@ -437,7 +486,7 @@ func (f *Fs) CreateDir(ctx context.Context, pathID, leaf string) (string, error)
err := f.pacer.CallNoRetry(func() (bool, error) { err := f.pacer.CallNoRetry(func() (bool, error) {
var err error var err error
resp, err = folders.CreateFolder(ctx, f.cfg, request) resp, err = folders.CreateFolder(ctx, f.cfg, request)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
// If folder already exists (409 conflict), try to find it // If folder already exists (409 conflict), try to find it
@@ -525,7 +574,7 @@ func (f *Fs) List(ctx context.Context, dir string) (fs.DirEntries, error) {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
var err error var err error
foldersList, err = folders.ListAllFolders(ctx, f.cfg, dirID) foldersList, err = folders.ListAllFolders(ctx, f.cfg, dirID)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -538,7 +587,7 @@ func (f *Fs) List(ctx context.Context, dir string) (fs.DirEntries, error) {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
var err error var err error
filesList, err = folders.ListAllFiles(ctx, f.cfg, dirID) filesList, err = folders.ListAllFiles(ctx, f.cfg, dirID)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -616,7 +665,7 @@ func (f *Fs) Remove(ctx context.Context, remote string) error {
} }
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
err := folders.DeleteFolder(ctx, f.cfg, dirID) err := folders.DeleteFolder(ctx, f.cfg, dirID)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return err return err
@@ -642,7 +691,7 @@ func (f *Fs) NewObject(ctx context.Context, remote string) (fs.Object, error) {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
var err error var err error
files, err = folders.ListAllFiles(ctx, f.cfg, dirID) files, err = folders.ListAllFiles(ctx, f.cfg, dirID)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -720,7 +769,7 @@ func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
err := f.pacer.Call(func() (bool, error) { err := f.pacer.Call(func() (bool, error) {
var err error var err error
internxtLimit, err = users.GetLimit(ctx, f.cfg) internxtLimit, err = users.GetLimit(ctx, f.cfg)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -730,7 +779,7 @@ func (f *Fs) About(ctx context.Context) (*fs.Usage, error) {
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
var err error var err error
internxtUsage, err = users.GetUsage(ctx, f.cfg) internxtUsage, err = users.GetUsage(ctx, f.cfg)
return shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -776,7 +825,7 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadClo
err := o.f.pacer.Call(func() (bool, error) { err := o.f.pacer.Call(func() (bool, error) {
var err error var err error
stream, err = buckets.DownloadFileStream(ctx, o.f.cfg, o.id, rangeValue) stream, err = buckets.DownloadFileStream(ctx, o.f.cfg, o.id, rangeValue)
return shouldRetry(ctx, err) return o.f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -826,7 +875,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
return false, nil return false, nil
} }
} }
return shouldRetry(ctx, err) return o.f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
return fmt.Errorf("failed to rename existing file to backup: %w", err) return fmt.Errorf("failed to rename existing file to backup: %w", err)
@@ -847,7 +896,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
src.Size(), src.Size(),
src.ModTime(ctx), src.ModTime(ctx),
) )
return shouldRetry(ctx, err) return o.f.shouldRetry(ctx, err)
}) })
if err != nil && isEmptyFileLimitError(err) { if err != nil && isEmptyFileLimitError(err) {
@@ -885,7 +934,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
} }
} }
} }
return shouldRetry(ctx, err) return o.f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
fs.Errorf(o.f, "Failed to delete backup file %s.%s (UUID: %s): %v. This may leave an orphaned backup file.", fs.Errorf(o.f, "Failed to delete backup file %s.%s (UUID: %s): %v. This may leave an orphaned backup file.",
@@ -939,7 +988,7 @@ func (o *Object) recoverFromTimeoutConflict(ctx context.Context, uploadErr error
checkErr := o.f.pacer.Call(func() (bool, error) { checkErr := o.f.pacer.Call(func() (bool, error) {
existingFile, err := o.f.preUploadCheck(ctx, encodedName, dirID) existingFile, err := o.f.preUploadCheck(ctx, encodedName, dirID)
if err != nil { if err != nil {
return shouldRetry(ctx, err) return o.f.shouldRetry(ctx, err)
} }
if existingFile != nil { if existingFile != nil {
name := strings.TrimSuffix(baseName, filepath.Ext(baseName)) name := strings.TrimSuffix(baseName, filepath.Ext(baseName))
@@ -978,7 +1027,7 @@ func (o *Object) restoreBackupFile(ctx context.Context, backupUUID, origName, or
_ = o.f.pacer.Call(func() (bool, error) { _ = o.f.pacer.Call(func() (bool, error) {
err := files.RenameFile(ctx, o.f.cfg, backupUUID, err := files.RenameFile(ctx, o.f.cfg, backupUUID,
o.f.opt.Encoding.FromStandardName(origName), origType) o.f.opt.Encoding.FromStandardName(origName), origType)
return shouldRetry(ctx, err) return o.f.shouldRetry(ctx, err)
}) })
} }
@@ -986,6 +1035,6 @@ func (o *Object) restoreBackupFile(ctx context.Context, backupUUID, origName, or
func (o *Object) Remove(ctx context.Context) error { func (o *Object) Remove(ctx context.Context) error {
return o.f.pacer.Call(func() (bool, error) { return o.f.pacer.Call(func() (bool, error) {
err := files.DeleteFile(ctx, o.f.cfg, o.uuid) err := files.DeleteFile(ctx, o.f.cfg, o.uuid)
return shouldRetry(ctx, err) return o.f.shouldRetry(ctx, err)
}) })
} }

View File

@@ -33,12 +33,10 @@ func TestRemove(t *testing.T) {
assert.True(t, exists()) assert.True(t, exists())
// close the file in the background // close the file in the background
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
require.NoError(t, fd.Close()) require.NoError(t, fd.Close())
}() })
// delete the open file // delete the open file
err = remove(name) err = remove(name)
require.NoError(t, err) require.NoError(t, err)

View File

@@ -14,7 +14,7 @@ import (
func remove(name string) (err error) { func remove(name string) (err error) {
const maxTries = 10 const maxTries = 10
var sleepTime = 1 * time.Millisecond var sleepTime = 1 * time.Millisecond
for i := 0; i < maxTries; i++ { for i := range maxTries {
err = os.Remove(name) err = os.Remove(name)
if err == nil { if err == nil {
break break

View File

@@ -112,7 +112,7 @@ type ListItem struct {
Count struct { Count struct {
Folders int `json:"folders"` Folders int `json:"folders"`
Files int `json:"files"` Files int `json:"files"`
} `json:"count,omitempty"` } `json:"count"`
Kind string `json:"kind"` Kind string `json:"kind"`
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
@@ -154,7 +154,7 @@ type FolderInfoResponse struct {
Type string `json:"type"` Type string `json:"type"`
Home string `json:"home"` Home string `json:"home"`
List []ListItem `json:"list"` List []ListItem `json:"list"`
} `json:"body,omitempty"` } `json:"body"`
Time int64 `json:"time"` Time int64 `json:"time"`
Status int `json:"status"` Status int `json:"status"`
Email string `json:"email"` Email string `json:"email"`

View File

@@ -50,12 +50,12 @@ type Identity struct {
// to represent a set of identities associated with various events for // to represent a set of identities associated with various events for
// an item, such as created by or last modified by. // an item, such as created by or last modified by.
type IdentitySet struct { type IdentitySet struct {
User Identity `json:"user,omitempty"` User Identity `json:"user"`
Application Identity `json:"application,omitempty"` Application Identity `json:"application"`
Device Identity `json:"device,omitempty"` Device Identity `json:"device"`
Group Identity `json:"group,omitempty"` Group Identity `json:"group"`
SiteGroup Identity `json:"siteGroup,omitempty"` // The SharePoint group associated with this action. Optional. SiteGroup Identity `json:"siteGroup"` // The SharePoint group associated with this action. Optional.
SiteUser Identity `json:"siteUser,omitempty"` // The SharePoint user associated with this action. Optional. SiteUser Identity `json:"siteUser"` // The SharePoint user associated with this action. Optional.
} }
// Quota groups storage space quota-related information on OneDrive into a single structure. // Quota groups storage space quota-related information on OneDrive into a single structure.
@@ -155,8 +155,8 @@ type FileFacet struct {
// facet can be used to specify the last modified date or created date // facet can be used to specify the last modified date or created date
// of the item as it was on the local device. // of the item as it was on the local device.
type FileSystemInfoFacet struct { type FileSystemInfoFacet struct {
CreatedDateTime Timestamp `json:"createdDateTime,omitempty"` // The UTC date and time the file was created on a client. CreatedDateTime Timestamp `json:"createdDateTime"` // The UTC date and time the file was created on a client.
LastModifiedDateTime Timestamp `json:"lastModifiedDateTime,omitempty"` // The UTC date and time the file was last modified on a client. LastModifiedDateTime Timestamp `json:"lastModifiedDateTime"` // The UTC date and time the file was last modified on a client.
} }
// DeletedFacet indicates that the item on OneDrive has been // DeletedFacet indicates that the item on OneDrive has been
@@ -175,10 +175,10 @@ type PackageFacet struct {
// SharedType indicates a DriveItem has been shared with others. The resource includes information about how the item is shared. // SharedType indicates a DriveItem has been shared with others. The resource includes information about how the item is shared.
// If a Driveitem has a non-null shared facet, the item has been shared. // If a Driveitem has a non-null shared facet, the item has been shared.
type SharedType struct { type SharedType struct {
Owner IdentitySet `json:"owner,omitempty"` // The identity of the owner of the shared item. Read-only. Owner IdentitySet `json:"owner"` // The identity of the owner of the shared item. Read-only.
Scope string `json:"scope,omitempty"` // Indicates the scope of how the item is shared: anonymous, organization, or users. Read-only. Scope string `json:"scope,omitempty"` // Indicates the scope of how the item is shared: anonymous, organization, or users. Read-only.
SharedBy IdentitySet `json:"sharedBy,omitempty"` // The identity of the user who shared the item. Read-only. SharedBy IdentitySet `json:"sharedBy"` // The identity of the user who shared the item. Read-only.
SharedDateTime Timestamp `json:"sharedDateTime,omitempty"` // The UTC date and time when the item was shared. Read-only. SharedDateTime Timestamp `json:"sharedDateTime"` // The UTC date and time when the item was shared. Read-only.
} }
// SharingInvitationType groups invitation-related data items into a single structure. // SharingInvitationType groups invitation-related data items into a single structure.

View File

@@ -75,8 +75,8 @@ type ErrorDetails struct {
Type string `json:"@type,omitempty"` Type string `json:"@type,omitempty"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
Domain string `json:"domain,omitempty"` Domain string `json:"domain,omitempty"`
Metadata struct{} `json:"metadata,omitempty"` // TODO: undiscovered yet Metadata struct{} `json:"metadata"` // TODO: undiscovered yet
Locale string `json:"locale,omitempty"` // e.g. "en" Locale string `json:"locale,omitempty"` // e.g. "en"
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
StackEntries []any `json:"stack_entries,omitempty"` // TODO: undiscovered yet StackEntries []any `json:"stack_entries,omitempty"` // TODO: undiscovered yet
Detail string `json:"detail,omitempty"` Detail string `json:"detail,omitempty"`
@@ -189,8 +189,8 @@ type File struct {
Apps []*FileApp `json:"apps,omitempty"` Apps []*FileApp `json:"apps,omitempty"`
Audit *FileAudit `json:"audit,omitempty"` Audit *FileAudit `json:"audit,omitempty"`
Collection string `json:"collection,omitempty"` // TODO Collection string `json:"collection,omitempty"` // TODO
CreatedTime Time `json:"created_time,omitempty"` CreatedTime Time `json:"created_time"`
DeleteTime Time `json:"delete_time,omitempty"` DeleteTime Time `json:"delete_time"`
FileCategory string `json:"file_category,omitempty"` // "AUDIO", "VIDEO" FileCategory string `json:"file_category,omitempty"` // "AUDIO", "VIDEO"
FileExtension string `json:"file_extension,omitempty"` FileExtension string `json:"file_extension,omitempty"`
FolderType string `json:"folder_type,omitempty"` FolderType string `json:"folder_type,omitempty"`
@@ -202,7 +202,7 @@ type File struct {
Md5Checksum string `json:"md5_checksum,omitempty"` Md5Checksum string `json:"md5_checksum,omitempty"`
Medias []*Media `json:"medias,omitempty"` Medias []*Media `json:"medias,omitempty"`
MimeType string `json:"mime_type,omitempty"` MimeType string `json:"mime_type,omitempty"`
ModifiedTime Time `json:"modified_time,omitempty"` // updated when renamed or moved ModifiedTime Time `json:"modified_time"` // updated when renamed or moved
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
OriginalFileIndex int `json:"original_file_index,omitempty"` // TODO OriginalFileIndex int `json:"original_file_index,omitempty"` // TODO
OriginalURL string `json:"original_url,omitempty"` OriginalURL string `json:"original_url,omitempty"`
@@ -221,7 +221,7 @@ type File struct {
ThumbnailLink string `json:"thumbnail_link,omitempty"` ThumbnailLink string `json:"thumbnail_link,omitempty"`
Trashed bool `json:"trashed,omitempty"` Trashed bool `json:"trashed,omitempty"`
UserID string `json:"user_id,omitempty"` UserID string `json:"user_id,omitempty"`
UserModifiedTime Time `json:"user_modified_time,omitempty"` UserModifiedTime Time `json:"user_modified_time"`
WebContentLink string `json:"web_content_link,omitempty"` WebContentLink string `json:"web_content_link,omitempty"`
Writable bool `json:"writable,omitempty"` Writable bool `json:"writable,omitempty"`
} }
@@ -252,7 +252,7 @@ type Media struct {
AudioCodec string `json:"audio_codec,omitempty"` // "pcm_bluray", "aac" AudioCodec string `json:"audio_codec,omitempty"` // "pcm_bluray", "aac"
VideoType string `json:"video_type,omitempty"` // "mpegts" VideoType string `json:"video_type,omitempty"` // "mpegts"
HdrType string `json:"hdr_type,omitempty"` HdrType string `json:"hdr_type,omitempty"`
} `json:"video,omitempty"` } `json:"video"`
Link *Link `json:"link,omitempty"` Link *Link `json:"link,omitempty"`
NeedMoreQuota bool `json:"need_more_quota,omitempty"` NeedMoreQuota bool `json:"need_more_quota,omitempty"`
VipTypes []any `json:"vip_types,omitempty"` // TODO maybe list of something? VipTypes []any `json:"vip_types,omitempty"` // TODO maybe list of something?
@@ -290,11 +290,11 @@ type FileApp struct {
NeedMoreQuota bool `json:"need_more_quota,omitempty"` NeedMoreQuota bool `json:"need_more_quota,omitempty"`
IconLink string `json:"icon_link,omitempty"` IconLink string `json:"icon_link,omitempty"`
IsDefault bool `json:"is_default,omitempty"` IsDefault bool `json:"is_default,omitempty"`
Params struct{} `json:"params,omitempty"` // TODO Params struct{} `json:"params"` // TODO
CategoryIDs []any `json:"category_ids,omitempty"` CategoryIDs []any `json:"category_ids,omitempty"`
AdSceneType int `json:"ad_scene_type,omitempty"` AdSceneType int `json:"ad_scene_type,omitempty"`
Space string `json:"space,omitempty"` Space string `json:"space,omitempty"`
Links struct{} `json:"links,omitempty"` // TODO Links struct{} `json:"links"` // TODO
} }
// ------------------------------------------------------------ // ------------------------------------------------------------
@@ -320,8 +320,8 @@ type Task struct {
FileName string `json:"file_name,omitempty"` FileName string `json:"file_name,omitempty"`
FileSize string `json:"file_size,omitempty"` FileSize string `json:"file_size,omitempty"`
Message string `json:"message,omitempty"` // e.g. "Saving" Message string `json:"message,omitempty"` // e.g. "Saving"
CreatedTime Time `json:"created_time,omitempty"` CreatedTime Time `json:"created_time"`
UpdatedTime Time `json:"updated_time,omitempty"` UpdatedTime Time `json:"updated_time"`
ThirdTaskID string `json:"third_task_id,omitempty"` // TODO ThirdTaskID string `json:"third_task_id,omitempty"` // TODO
Phase string `json:"phase,omitempty"` // e.g. "PHASE_TYPE_RUNNING" Phase string `json:"phase,omitempty"` // e.g. "PHASE_TYPE_RUNNING"
Progress int `json:"progress,omitempty"` Progress int `json:"progress,omitempty"`
@@ -368,7 +368,7 @@ type ResumableParams struct {
AccessKeySecret string `json:"access_key_secret,omitempty"` AccessKeySecret string `json:"access_key_secret,omitempty"`
Bucket string `json:"bucket,omitempty"` Bucket string `json:"bucket,omitempty"`
Endpoint string `json:"endpoint,omitempty"` Endpoint string `json:"endpoint,omitempty"`
Expiration Time `json:"expiration,omitempty"` Expiration Time `json:"expiration"`
Key string `json:"key,omitempty"` Key string `json:"key,omitempty"`
SecurityToken string `json:"security_token,omitempty"` SecurityToken string `json:"security_token,omitempty"`
} }
@@ -409,7 +409,7 @@ type About struct {
Kind string `json:"kind,omitempty"` // "drive#about" Kind string `json:"kind,omitempty"` // "drive#about"
Quota *Quota `json:"quota,omitempty"` Quota *Quota `json:"quota,omitempty"`
ExpiresAt string `json:"expires_at,omitempty"` ExpiresAt string `json:"expires_at,omitempty"`
Quotas struct{} `json:"quotas,omitempty"` // maybe []*Quota? Quotas struct{} `json:"quotas"` // maybe []*Quota?
} }
// Quota informs drive quota // Quota informs drive quota
@@ -445,8 +445,8 @@ type User struct {
PhoneNumber string `json:"phone_number,omitempty"` PhoneNumber string `json:"phone_number,omitempty"`
Password string `json:"password,omitempty"` // "SET" if configured Password string `json:"password,omitempty"` // "SET" if configured
Status string `json:"status,omitempty"` // "ACTIVE" Status string `json:"status,omitempty"` // "ACTIVE"
CreatedAt Time `json:"created_at,omitempty"` CreatedAt Time `json:"created_at"`
PasswordUpdatedAt Time `json:"password_updated_at,omitempty"` PasswordUpdatedAt Time `json:"password_updated_at"`
} }
// UserProvider details third-party authentication // UserProvider details third-party authentication
@@ -464,11 +464,11 @@ type VIP struct {
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
RedirectURI string `json:"redirect_uri,omitempty"` RedirectURI string `json:"redirect_uri,omitempty"`
Data struct { Data struct {
Expire Time `json:"expire,omitempty"` Expire Time `json:"expire"`
Status string `json:"status,omitempty"` // "invalid" or "ok" Status string `json:"status,omitempty"` // "invalid" or "ok"
Type string `json:"type,omitempty"` // "novip" or "platinum" Type string `json:"type,omitempty"` // "novip" or "platinum"
UserID string `json:"user_id,omitempty"` // same as User.Sub UserID string `json:"user_id,omitempty"` // same as User.Sub
} `json:"data,omitempty"` } `json:"data"`
} }
// DecompressResult is a response to RequestDecompress // DecompressResult is a response to RequestDecompress
@@ -538,7 +538,7 @@ type CaptchaToken struct {
CaptchaToken string `json:"captcha_token"` CaptchaToken string `json:"captcha_token"`
ExpiresIn int64 `json:"expires_in"` // currently 300s ExpiresIn int64 `json:"expires_in"` // currently 300s
// API doesn't provide Expiry field and thus it should be populated from ExpiresIn on retrieval // API doesn't provide Expiry field and thus it should be populated from ExpiresIn on retrieval
Expiry time.Time `json:"expiry,omitempty"` Expiry time.Time `json:"expiry"`
URL string `json:"url,omitempty"` // a link for users to solve captcha URL string `json:"url,omitempty"` // a link for users to solve captcha
} }

View File

@@ -1608,7 +1608,7 @@ func (f *Fs) UserInfo(ctx context.Context) (userInfo map[string]string, err erro
// ------------------------------------------------------------ // ------------------------------------------------------------
// add offline download task for url // add offline download task for url
func (f *Fs) addURL(ctx context.Context, url, path string) (*api.Task, error) { func (f *Fs) addURL(ctx context.Context, url, name, path string) (*api.Task, error) {
req := api.RequestNewTask{ req := api.RequestNewTask{
Kind: api.KindOfFile, Kind: api.KindOfFile,
UploadType: "UPLOAD_TYPE_URL", UploadType: "UPLOAD_TYPE_URL",
@@ -1617,6 +1617,9 @@ func (f *Fs) addURL(ctx context.Context, url, path string) (*api.Task, error) {
}, },
FolderType: "DOWNLOAD", FolderType: "DOWNLOAD",
} }
if name != "" {
req.Name = f.opt.Enc.FromStandardName(name)
}
if parentID, err := f.dirCache.FindDir(ctx, path, false); err == nil { if parentID, err := f.dirCache.FindDir(ctx, path, false); err == nil {
req.ParentID = parentIDForRequest(parentID) req.ParentID = parentIDForRequest(parentID)
req.FolderType = "" req.FolderType = ""
@@ -1681,14 +1684,18 @@ var commandHelp = []fs.CommandHelp{{
Short: "Add offline download task for url.", Short: "Add offline download task for url.",
Long: `This command adds offline download task for url. Long: `This command adds offline download task for url.
Usage example: Usage examples:
` + "```console" + ` ` + "```console" + `
rclone backend addurl pikpak:dirpath url rclone backend addurl pikpak:dirpath url
rclone backend addurl pikpak:dirpath url -o name=custom_filename.zip
` + "```" + ` ` + "```" + `
Downloads will be stored in 'dirpath'. If 'dirpath' is invalid, Downloads will be stored in 'dirpath'. If 'dirpath' is invalid,
download will fallback to default 'My Pack' folder.`, download will fallback to default 'My Pack' folder.`,
Opts: map[string]string{
"name": "Custom filename for the downloaded file.",
},
}, { }, {
Name: "decompress", Name: "decompress",
Short: "Request decompress of a file/files in a folder.", Short: "Request decompress of a file/files in a folder.",
@@ -1732,7 +1739,11 @@ func (f *Fs) Command(ctx context.Context, name string, arg []string, opt map[str
if len(arg) != 1 { if len(arg) != 1 {
return nil, errors.New("need exactly 1 argument") return nil, errors.New("need exactly 1 argument")
} }
return f.addURL(ctx, arg[0], "") filename := ""
if name, ok := opt["name"]; ok {
filename = name
}
return f.addURL(ctx, arg[0], filename, "")
case "decompress": case "decompress":
filename := "" filename := ""
if len(arg) > 0 { if len(arg) > 0 {

View File

@@ -170,6 +170,7 @@ In `backend/s3/provider/YourProvider.yaml`
UseUnsignedPayload *bool `yaml:"use_unsigned_payload,omitempty"` UseUnsignedPayload *bool `yaml:"use_unsigned_payload,omitempty"`
UseXID *bool `yaml:"use_x_id,omitempty"` UseXID *bool `yaml:"use_x_id,omitempty"`
SignAcceptEncoding *bool `yaml:"sign_accept_encoding,omitempty"` SignAcceptEncoding *bool `yaml:"sign_accept_encoding,omitempty"`
EtagIsNotMD5 *bool `yaml:"etag_is_not_md5,omitempty"`
CopyCutoff *int64 `yaml:"copy_cutoff,omitempty"` CopyCutoff *int64 `yaml:"copy_cutoff,omitempty"`
MaxUploadParts *int `yaml:"max_upload_parts,omitempty"` MaxUploadParts *int `yaml:"max_upload_parts,omitempty"`
MinChunkSize *int64 `yaml:"min_chunk_size,omitempty"` MinChunkSize *int64 `yaml:"min_chunk_size,omitempty"`

View File

@@ -0,0 +1,16 @@
name: Fastly
description: Fastly Object Storage
region:
us-east: US East
us-west: US West
eu-central: EU Central
endpoint:
us-east.object.fastlystorage.app: US East
us-west.object.fastlystorage.app: US West
eu-central.object.fastlystorage.app: EU Central
quirks:
force_path_style: true
use_already_exists: false
use_multipart_etag: false
use_multipart_uploads: false
etag_is_not_md5: true

View File

@@ -15,7 +15,7 @@ endpoint:
acl: {} acl: {}
bucket_acl: true bucket_acl: true
quirks: quirks:
list_version: 1 list_version: 2
force_path_style: true force_path_style: true
list_url_encode: false list_url_encode: false
use_multipart_etag: false use_multipart_etag: false

View File

@@ -1,14 +0,0 @@
name: StackPath
description: StackPath Object Storage
region: {}
endpoint:
s3.us-east-2.stackpathstorage.com: US East Endpoint
s3.us-west-1.stackpathstorage.com: US West Endpoint
s3.eu-central-1.stackpathstorage.com: EU Endpoint
acl: {}
bucket_acl: true
quirks:
list_version: 1
force_path_style: true
list_url_encode: false
use_already_exists: false

View File

@@ -0,0 +1,9 @@
name: Zadara
description: Zadara Object Storage
region:
us-east-1: |-
The default region.
Leave location constraint empty.
endpoint: {}
quirks:
force_path_style: true

View File

@@ -32,6 +32,7 @@ type Quirks struct {
UseUnsignedPayload *bool `yaml:"use_unsigned_payload,omitempty"` UseUnsignedPayload *bool `yaml:"use_unsigned_payload,omitempty"`
UseXID *bool `yaml:"use_x_id,omitempty"` UseXID *bool `yaml:"use_x_id,omitempty"`
SignAcceptEncoding *bool `yaml:"sign_accept_encoding,omitempty"` SignAcceptEncoding *bool `yaml:"sign_accept_encoding,omitempty"`
EtagIsNotMD5 *bool `yaml:"etag_is_not_md5,omitempty"`
CopyCutoff *int64 `yaml:"copy_cutoff,omitempty"` CopyCutoff *int64 `yaml:"copy_cutoff,omitempty"`
MaxUploadParts *int `yaml:"max_upload_parts,omitempty"` MaxUploadParts *int `yaml:"max_upload_parts,omitempty"`
MinChunkSize *int64 `yaml:"min_chunk_size,omitempty"` MinChunkSize *int64 `yaml:"min_chunk_size,omitempty"`

View File

@@ -828,6 +828,107 @@ use |-vv| to see the debug level logs.
}, { }, {
Name: "ibm_resource_instance_id", Name: "ibm_resource_instance_id",
Help: "IBM service instance id", Help: "IBM service instance id",
}, {
Name: "object_lock_mode",
Help: `Object Lock mode to apply when uploading or copying objects.
Set this to apply Object Lock retention mode to objects.
If not set, no Object Lock mode is applied (even with --metadata).
Note: To enable Object Lock retention, you must set BOTH object_lock_mode
AND object_lock_retain_until_date. Setting only one has no effect.
- GOVERNANCE: Set Object Lock mode to GOVERNANCE
- COMPLIANCE: Set Object Lock mode to COMPLIANCE
- copy: Copy the mode from the source object (requires --metadata)
See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html`,
Advanced: true,
Examples: []fs.OptionExample{{
Value: "GOVERNANCE",
Help: "Set Object Lock mode to GOVERNANCE",
}, {
Value: "COMPLIANCE",
Help: "Set Object Lock mode to COMPLIANCE",
}, {
Value: "copy",
Help: "Copy from source object (requires --metadata)",
}},
}, {
Name: "object_lock_retain_until_date",
Help: `Object Lock retention until date to apply when uploading or copying objects.
Set this to apply Object Lock retention date to objects.
If not set, no retention date is applied (even with --metadata).
Note: To enable Object Lock retention, you must set BOTH object_lock_mode
AND object_lock_retain_until_date. Setting only one has no effect.
Accepts:
- RFC 3339 format: 2030-01-02T15:04:05Z
- Duration from now: 365d, 1y, 6M (days, years, months)
- copy: Copy the date from the source object (requires --metadata)
See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html`,
Advanced: true,
Examples: []fs.OptionExample{{
Value: "copy",
Help: "Copy from source object (requires --metadata)",
}, {
Value: "2030-01-01T00:00:00Z",
Help: "Set specific date (RFC 3339 format)",
}, {
Value: "365d",
Help: "Set retention for 365 days from now",
}, {
Value: "1y",
Help: "Set retention for 1 year from now",
}},
}, {
Name: "object_lock_legal_hold_status",
Help: `Object Lock legal hold status to apply when uploading or copying objects.
Set this to apply Object Lock legal hold to objects.
If not set, no legal hold is applied (even with --metadata).
Note: Legal hold is independent of retention and can be set separately.
- ON: Enable legal hold
- OFF: Disable legal hold
- copy: Copy the legal hold status from the source object (requires --metadata)
See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html`,
Advanced: true,
Examples: []fs.OptionExample{{
Value: "ON",
Help: "Enable legal hold",
}, {
Value: "OFF",
Help: "Disable legal hold",
}, {
Value: "copy",
Help: "Copy from source object (requires --metadata)",
}},
}, {
Name: "bypass_governance_retention",
Help: `Allow deleting or modifying objects locked with GOVERNANCE mode.`,
Default: false,
Advanced: true,
}, {
Name: "bucket_object_lock_enabled",
Help: `Enable Object Lock when creating new buckets.`,
Default: false,
}, {
Name: "object_lock_set_after_upload",
Help: `Set Object Lock via separate API calls after upload.
Use this for S3-compatible providers that don't support setting Object Lock
headers during PUT operations. When enabled, Object Lock is set via separate
PutObjectRetention and PutObjectLegalHold API calls after the upload completes.
This adds extra API calls per object, so only enable if your provider requires it.`,
Default: false,
Advanced: true,
}, },
}})) }}))
} }
@@ -922,6 +1023,21 @@ var systemMetadataInfo = map[string]fs.MetadataHelp{
Example: "2006-01-02T15:04:05.999999999Z07:00", Example: "2006-01-02T15:04:05.999999999Z07:00",
ReadOnly: true, ReadOnly: true,
}, },
"object-lock-mode": {
Help: "Object Lock mode: GOVERNANCE or COMPLIANCE",
Type: "string",
Example: "GOVERNANCE",
},
"object-lock-retain-until-date": {
Help: "Object Lock retention until date",
Type: "RFC 3339",
Example: "2030-01-02T15:04:05Z",
},
"object-lock-legal-hold-status": {
Help: "Object Lock legal hold status: ON or OFF",
Type: "string",
Example: "OFF",
},
} }
// Options defines the configuration for this backend // Options defines the configuration for this backend
@@ -992,6 +1108,12 @@ type Options struct {
IBMInstanceID string `config:"ibm_resource_instance_id"` IBMInstanceID string `config:"ibm_resource_instance_id"`
UseXID fs.Tristate `config:"use_x_id"` UseXID fs.Tristate `config:"use_x_id"`
SignAcceptEncoding fs.Tristate `config:"sign_accept_encoding"` SignAcceptEncoding fs.Tristate `config:"sign_accept_encoding"`
ObjectLockMode string `config:"object_lock_mode"`
ObjectLockRetainUntilDate string `config:"object_lock_retain_until_date"`
ObjectLockLegalHoldStatus string `config:"object_lock_legal_hold_status"`
BypassGovernanceRetention bool `config:"bypass_governance_retention"`
BucketObjectLockEnabled bool `config:"bucket_object_lock_enabled"`
ObjectLockSetAfterUpload bool `config:"object_lock_set_after_upload"`
} }
// Fs represents a remote s3 server // Fs represents a remote s3 server
@@ -1036,6 +1158,11 @@ type Object struct {
contentDisposition *string // Content-Disposition: header contentDisposition *string // Content-Disposition: header
contentEncoding *string // Content-Encoding: header contentEncoding *string // Content-Encoding: header
contentLanguage *string // Content-Language: header contentLanguage *string // Content-Language: header
// Object Lock metadata
objectLockMode *string // Object Lock mode: GOVERNANCE or COMPLIANCE
objectLockRetainUntilDate *time.Time // Object Lock retention until date
objectLockLegalHoldStatus *string // Object Lock legal hold: ON or OFF
} }
// safely dereference the pointer, returning a zero T if nil // safely dereference the pointer, returning a zero T if nil
@@ -1056,6 +1183,21 @@ func getHTTPStatusCode(err error) int {
return -1 return -1
} }
// parseRetainUntilDate parses a retain until date from a string.
// It accepts RFC 3339 format or duration strings like "365d", "1y", "6m".
func parseRetainUntilDate(s string) (time.Time, error) {
// First try RFC 3339 format
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t, nil
}
// Try as a duration from now
d, err := fs.ParseDuration(s)
if err != nil {
return time.Time{}, fmt.Errorf("can't parse %q as RFC 3339 date or duration: %w", s, err)
}
return time.Now().Add(d), nil
}
// ------------------------------------------------------------ // ------------------------------------------------------------
// Name of the remote (as passed into NewFs) // Name of the remote (as passed into NewFs)
@@ -1664,6 +1806,10 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
// MD5 digest of their object data. // MD5 digest of their object data.
f.etagIsNotMD5 = true f.etagIsNotMD5 = true
} }
if provider.Quirks.EtagIsNotMD5 != nil && *provider.Quirks.EtagIsNotMD5 {
// Provider always returns ETags that are not MD5 (e.g., mandatory encryption)
f.etagIsNotMD5 = true
}
if opt.DirectoryBucket { if opt.DirectoryBucket {
// Objects uploaded to directory buckets appear to have random ETags // Objects uploaded to directory buckets appear to have random ETags
// //
@@ -1688,6 +1834,9 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
if opt.Provider == "AWS" { if opt.Provider == "AWS" {
f.features.DoubleSlash = true f.features.DoubleSlash = true
} }
if opt.Provider == "Fastly" {
f.features.Copy = nil
}
if opt.Provider == "Rabata" { if opt.Provider == "Rabata" {
f.features.Copy = nil f.features.Copy = nil
} }
@@ -2642,8 +2791,9 @@ func (f *Fs) makeBucket(ctx context.Context, bucket string) error {
} }
return f.cache.Create(bucket, func() error { return f.cache.Create(bucket, func() error {
req := s3.CreateBucketInput{ req := s3.CreateBucketInput{
Bucket: &bucket, Bucket: &bucket,
ACL: types.BucketCannedACL(f.opt.BucketACL), ACL: types.BucketCannedACL(f.opt.BucketACL),
ObjectLockEnabledForBucket: &f.opt.BucketObjectLockEnabled,
} }
if f.opt.LocationConstraint != "" { if f.opt.LocationConstraint != "" {
req.CreateBucketConfiguration = &types.CreateBucketConfiguration{ req.CreateBucketConfiguration = &types.CreateBucketConfiguration{
@@ -2762,6 +2912,24 @@ func (f *Fs) copy(ctx context.Context, req *s3.CopyObjectInput, dstBucket, dstPa
req.StorageClass = types.StorageClass(f.opt.StorageClass) req.StorageClass = types.StorageClass(f.opt.StorageClass)
} }
// Apply Object Lock options via headers (unless ObjectLockSetAfterUpload is set)
// "copy" means: keep the value from source (passed via req from prepareUpload/setFrom functions)
if !f.opt.ObjectLockSetAfterUpload {
if f.opt.ObjectLockMode != "" && !strings.EqualFold(f.opt.ObjectLockMode, "copy") {
req.ObjectLockMode = types.ObjectLockMode(strings.ToUpper(f.opt.ObjectLockMode))
}
if f.opt.ObjectLockRetainUntilDate != "" && !strings.EqualFold(f.opt.ObjectLockRetainUntilDate, "copy") {
retainDate, err := parseRetainUntilDate(f.opt.ObjectLockRetainUntilDate)
if err != nil {
return fmt.Errorf("invalid object_lock_retain_until_date: %w", err)
}
req.ObjectLockRetainUntilDate = &retainDate
}
if f.opt.ObjectLockLegalHoldStatus != "" && !strings.EqualFold(f.opt.ObjectLockLegalHoldStatus, "copy") {
req.ObjectLockLegalHoldStatus = types.ObjectLockLegalHoldStatus(strings.ToUpper(f.opt.ObjectLockLegalHoldStatus))
}
}
if src.bytes >= int64(f.opt.CopyCutoff) { if src.bytes >= int64(f.opt.CopyCutoff) {
return f.copyMultipart(ctx, req, dstBucket, dstPath, srcBucket, srcPath, src) return f.copyMultipart(ctx, req, dstBucket, dstPath, srcBucket, srcPath, src)
} }
@@ -2944,7 +3112,15 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
} }
setFrom_s3CopyObjectInput_s3PutObjectInput(&req, ui.req) setFrom_s3CopyObjectInput_s3PutObjectInput(&req, ui.req)
if ci.Metadata { // Use REPLACE directive if metadata is being modified, otherwise S3 ignores our values
// This is needed when:
// 1. --metadata flag is set
// 2. Any Object Lock option is set (to override or explicitly copy)
needsReplace := ci.Metadata ||
f.opt.ObjectLockMode != "" ||
f.opt.ObjectLockRetainUntilDate != "" ||
f.opt.ObjectLockLegalHoldStatus != ""
if needsReplace {
req.MetadataDirective = types.MetadataDirectiveReplace req.MetadataDirective = types.MetadataDirectiveReplace
} }
@@ -2952,7 +3128,21 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
if err != nil { if err != nil {
return nil, err return nil, err
} }
return f.NewObject(ctx, remote) dstObj, err := f.NewObject(ctx, remote)
if err != nil {
return nil, err
}
// Set Object Lock via separate API calls if requested
if f.opt.ObjectLockSetAfterUpload {
if dstObject, ok := dstObj.(*Object); ok {
if err := dstObject.setObjectLockAfterUpload(ctx, srcObj); err != nil {
return nil, err
}
}
}
return dstObj, nil
} }
// Hashes returns the supported hash sets. // Hashes returns the supported hash sets.
@@ -3831,6 +4021,19 @@ func (o *Object) setMetaData(resp *s3.HeadObjectOutput) {
o.contentEncoding = stringClonePointer(removeAWSChunked(resp.ContentEncoding)) o.contentEncoding = stringClonePointer(removeAWSChunked(resp.ContentEncoding))
o.contentLanguage = stringClonePointer(resp.ContentLanguage) o.contentLanguage = stringClonePointer(resp.ContentLanguage)
// Set Object Lock metadata
if resp.ObjectLockMode != "" {
mode := string(resp.ObjectLockMode)
o.objectLockMode = &mode
}
if resp.ObjectLockRetainUntilDate != nil {
o.objectLockRetainUntilDate = resp.ObjectLockRetainUntilDate
}
if resp.ObjectLockLegalHoldStatus != "" {
status := string(resp.ObjectLockLegalHoldStatus)
o.objectLockLegalHoldStatus = &status
}
// If decompressing then size and md5sum are unknown // If decompressing then size and md5sum are unknown
if o.fs.opt.Decompress && deref(o.contentEncoding) == "gzip" { if o.fs.opt.Decompress && deref(o.contentEncoding) == "gzip" {
o.bytes = -1 o.bytes = -1
@@ -4549,6 +4752,26 @@ func (o *Object) prepareUpload(ctx context.Context, src fs.ObjectInfo, options [
case "btime": case "btime":
// write as metadata since we can't set it // write as metadata since we can't set it
ui.req.Metadata[k] = v ui.req.Metadata[k] = v
case "object-lock-mode":
// Only apply if option is set to "copy" and not using after-upload API
if strings.EqualFold(o.fs.opt.ObjectLockMode, "copy") && !o.fs.opt.ObjectLockSetAfterUpload {
ui.req.ObjectLockMode = types.ObjectLockMode(v)
}
case "object-lock-retain-until-date":
// Only apply if option is set to "copy" and not using after-upload API
if strings.EqualFold(o.fs.opt.ObjectLockRetainUntilDate, "copy") && !o.fs.opt.ObjectLockSetAfterUpload {
retainDate, err := time.Parse(time.RFC3339, v)
if err != nil {
fs.Debugf(o, "failed to parse object-lock-retain-until-date %q: %v", v, err)
} else {
ui.req.ObjectLockRetainUntilDate = &retainDate
}
}
case "object-lock-legal-hold-status":
// Only apply if option is set to "copy" and not using after-upload API
if strings.EqualFold(o.fs.opt.ObjectLockLegalHoldStatus, "copy") && !o.fs.opt.ObjectLockSetAfterUpload {
ui.req.ObjectLockLegalHoldStatus = types.ObjectLockLegalHoldStatus(v)
}
default: default:
ui.req.Metadata[k] = v ui.req.Metadata[k] = v
} }
@@ -4614,6 +4837,25 @@ func (o *Object) prepareUpload(ctx context.Context, src fs.ObjectInfo, options [
if o.fs.opt.StorageClass != "" { if o.fs.opt.StorageClass != "" {
ui.req.StorageClass = types.StorageClass(o.fs.opt.StorageClass) ui.req.StorageClass = types.StorageClass(o.fs.opt.StorageClass)
} }
// Apply Object Lock options via headers (unless ObjectLockSetAfterUpload is set)
// "copy" means: keep the value from metadata (already applied above in the switch)
if !o.fs.opt.ObjectLockSetAfterUpload {
if o.fs.opt.ObjectLockMode != "" && !strings.EqualFold(o.fs.opt.ObjectLockMode, "copy") {
ui.req.ObjectLockMode = types.ObjectLockMode(strings.ToUpper(o.fs.opt.ObjectLockMode))
}
if o.fs.opt.ObjectLockRetainUntilDate != "" && !strings.EqualFold(o.fs.opt.ObjectLockRetainUntilDate, "copy") {
retainDate, err := parseRetainUntilDate(o.fs.opt.ObjectLockRetainUntilDate)
if err != nil {
return ui, fmt.Errorf("invalid object_lock_retain_until_date %q: %w", o.fs.opt.ObjectLockRetainUntilDate, err)
}
ui.req.ObjectLockRetainUntilDate = &retainDate
}
if o.fs.opt.ObjectLockLegalHoldStatus != "" && !strings.EqualFold(o.fs.opt.ObjectLockLegalHoldStatus, "copy") {
ui.req.ObjectLockLegalHoldStatus = types.ObjectLockLegalHoldStatus(strings.ToUpper(o.fs.opt.ObjectLockLegalHoldStatus))
}
}
// Apply upload options // Apply upload options
for _, option := range options { for _, option := range options {
key, value := option.Header() key, value := option.Header()
@@ -4737,6 +4979,14 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
} }
fs.Debugf(o, "Multipart upload Etag: %s OK", wantETag) fs.Debugf(o, "Multipart upload Etag: %s OK", wantETag)
} }
// Set Object Lock via separate API calls if requested
if o.fs.opt.ObjectLockSetAfterUpload {
if err := o.setObjectLockAfterUpload(ctx, src); err != nil {
return err
}
}
return err return err
} }
@@ -4754,6 +5004,9 @@ func (o *Object) Remove(ctx context.Context) error {
if o.fs.opt.RequesterPays { if o.fs.opt.RequesterPays {
req.RequestPayer = types.RequestPayerRequester req.RequestPayer = types.RequestPayerRequester
} }
if o.fs.opt.BypassGovernanceRetention {
req.BypassGovernanceRetention = &o.fs.opt.BypassGovernanceRetention
}
err := o.fs.pacer.Call(func() (bool, error) { err := o.fs.pacer.Call(func() (bool, error) {
_, err := o.fs.c.DeleteObject(ctx, &req) _, err := o.fs.c.DeleteObject(ctx, &req)
return o.fs.shouldRetry(ctx, err) return o.fs.shouldRetry(ctx, err)
@@ -4761,6 +5014,120 @@ func (o *Object) Remove(ctx context.Context) error {
return err return err
} }
// setObjectRetention sets Object Lock retention on an object via PutObjectRetention API
//
// Note: We use smithyhttp.AddContentChecksumMiddleware to ensure Content-MD5 is
// calculated for the request body. The AWS SDK v2 switched from MD5 to CRC32 as
// the default checksum algorithm, but some S3-compatible providers (e.g. MinIO)
// still require Content-MD5 for PutObjectRetention requests.
// See: https://github.com/aws/aws-sdk-go-v2/discussions/2960
func (o *Object) setObjectRetention(ctx context.Context, mode types.ObjectLockRetentionMode, retainUntilDate time.Time) error {
bucket, bucketPath := o.split()
req := s3.PutObjectRetentionInput{
Bucket: &bucket,
Key: &bucketPath,
VersionId: o.versionID,
Retention: &types.ObjectLockRetention{
Mode: mode,
RetainUntilDate: &retainUntilDate,
},
}
if o.fs.opt.RequesterPays {
req.RequestPayer = types.RequestPayerRequester
}
if o.fs.opt.BypassGovernanceRetention {
req.BypassGovernanceRetention = &o.fs.opt.BypassGovernanceRetention
}
return o.fs.pacer.Call(func() (bool, error) {
_, err := o.fs.c.PutObjectRetention(ctx, &req,
s3.WithAPIOptions(smithyhttp.AddContentChecksumMiddleware))
return o.fs.shouldRetry(ctx, err)
})
}
// setObjectLegalHold sets Object Lock legal hold on an object via PutObjectLegalHold API
//
// Note: We use smithyhttp.AddContentChecksumMiddleware to ensure Content-MD5 is
// calculated for the request body. The AWS SDK v2 switched from MD5 to CRC32 as
// the default checksum algorithm, but some S3-compatible providers (e.g. MinIO)
// still require Content-MD5 for PutObjectLegalHold requests.
// See: https://github.com/aws/aws-sdk-go-v2/discussions/2960
func (o *Object) setObjectLegalHold(ctx context.Context, status types.ObjectLockLegalHoldStatus) error {
bucket, bucketPath := o.split()
req := s3.PutObjectLegalHoldInput{
Bucket: &bucket,
Key: &bucketPath,
VersionId: o.versionID,
LegalHold: &types.ObjectLockLegalHold{
Status: status,
},
}
if o.fs.opt.RequesterPays {
req.RequestPayer = types.RequestPayerRequester
}
return o.fs.pacer.Call(func() (bool, error) {
_, err := o.fs.c.PutObjectLegalHold(ctx, &req,
s3.WithAPIOptions(smithyhttp.AddContentChecksumMiddleware))
return o.fs.shouldRetry(ctx, err)
})
}
// setObjectLockAfterUpload sets Object Lock via separate API calls after upload
// This is for S3 providers that don't support Object Lock headers during PUT
func (o *Object) setObjectLockAfterUpload(ctx context.Context, src fs.ObjectInfo) error {
// Determine the mode
var mode types.ObjectLockRetentionMode
modeOpt := o.fs.opt.ObjectLockMode
if strings.EqualFold(modeOpt, "copy") {
if srcObj, ok := src.(*Object); ok && srcObj.objectLockMode != nil {
mode = types.ObjectLockRetentionMode(*srcObj.objectLockMode)
}
} else if modeOpt != "" {
mode = types.ObjectLockRetentionMode(strings.ToUpper(modeOpt))
}
// Determine the retain until date
var retainUntilDate time.Time
dateOpt := o.fs.opt.ObjectLockRetainUntilDate
if strings.EqualFold(dateOpt, "copy") {
if srcObj, ok := src.(*Object); ok && srcObj.objectLockRetainUntilDate != nil {
retainUntilDate = *srcObj.objectLockRetainUntilDate
}
} else if dateOpt != "" {
var err error
retainUntilDate, err = parseRetainUntilDate(dateOpt)
if err != nil {
return fmt.Errorf("invalid object_lock_retain_until_date %q: %w", dateOpt, err)
}
}
// Set retention if both mode and date are set
if mode != "" && !retainUntilDate.IsZero() {
if err := o.setObjectRetention(ctx, mode, retainUntilDate); err != nil {
return fmt.Errorf("failed to set object retention: %w", err)
}
}
// Determine and set legal hold
var legalHold types.ObjectLockLegalHoldStatus
legalHoldOpt := o.fs.opt.ObjectLockLegalHoldStatus
if strings.EqualFold(legalHoldOpt, "copy") {
if srcObj, ok := src.(*Object); ok && srcObj.objectLockLegalHoldStatus != nil {
legalHold = types.ObjectLockLegalHoldStatus(*srcObj.objectLockLegalHoldStatus)
}
} else if legalHoldOpt != "" {
legalHold = types.ObjectLockLegalHoldStatus(strings.ToUpper(legalHoldOpt))
}
if legalHold != "" {
if err := o.setObjectLegalHold(ctx, legalHold); err != nil {
return fmt.Errorf("failed to set legal hold: %w", err)
}
}
return nil
}
// MimeType of an Object if known, "" otherwise // MimeType of an Object if known, "" otherwise
func (o *Object) MimeType(ctx context.Context) string { func (o *Object) MimeType(ctx context.Context) string {
err := o.readMetaData(ctx) err := o.readMetaData(ctx)
@@ -4804,7 +5171,7 @@ func (o *Object) Metadata(ctx context.Context) (metadata fs.Metadata, err error)
if err != nil { if err != nil {
return nil, err return nil, err
} }
metadata = make(fs.Metadata, len(o.meta)+7) metadata = make(fs.Metadata, len(o.meta)+10)
for k, v := range o.meta { for k, v := range o.meta {
switch k { switch k {
case metaMtime: case metaMtime:
@@ -4839,6 +5206,15 @@ func (o *Object) Metadata(ctx context.Context) (metadata fs.Metadata, err error)
setMetadata("content-disposition", o.contentDisposition) setMetadata("content-disposition", o.contentDisposition)
setMetadata("content-encoding", o.contentEncoding) setMetadata("content-encoding", o.contentEncoding)
setMetadata("content-language", o.contentLanguage) setMetadata("content-language", o.contentLanguage)
// Set Object Lock metadata
setMetadata("object-lock-mode", o.objectLockMode)
if o.objectLockRetainUntilDate != nil {
formatted := o.objectLockRetainUntilDate.Format(time.RFC3339)
setMetadata("object-lock-retain-until-date", &formatted)
}
setMetadata("object-lock-legal-hold-status", o.objectLockLegalHoldStatus)
metadata["tier"] = o.GetTier() metadata["tier"] = o.GetTier()
return metadata, nil return metadata, nil

View File

@@ -498,10 +498,236 @@ func (f *Fs) InternalTestVersions(t *testing.T) {
// Purge gets tested later // Purge gets tested later
} }
func (f *Fs) InternalTestObjectLock(t *testing.T) {
ctx := context.Background()
// Create a temporary bucket with Object Lock enabled to test on.
// This exercises our BucketObjectLockEnabled option and isolates
// the test from the main test bucket.
lockBucket := f.rootBucket + "-object-lock-" + random.String(8)
lockBucket = strings.ToLower(lockBucket)
// Try to create bucket with Object Lock enabled
objectLockEnabled := true
req := s3.CreateBucketInput{
Bucket: &lockBucket,
ACL: types.BucketCannedACL(f.opt.BucketACL),
ObjectLockEnabledForBucket: &objectLockEnabled,
}
if f.opt.LocationConstraint != "" {
req.CreateBucketConfiguration = &types.CreateBucketConfiguration{
LocationConstraint: types.BucketLocationConstraint(f.opt.LocationConstraint),
}
}
err := f.pacer.Call(func() (bool, error) {
_, err := f.c.CreateBucket(ctx, &req)
return f.shouldRetry(ctx, err)
})
if err != nil {
t.Skipf("Object Lock not supported by this provider: CreateBucket with Object Lock failed: %v", err)
}
// Verify Object Lock is actually enabled on the new bucket.
// Some S3-compatible servers (e.g. rclone serve s3) accept the
// ObjectLockEnabledForBucket flag but don't actually implement Object Lock.
var lockCfg *s3.GetObjectLockConfigurationOutput
err = f.pacer.Call(func() (bool, error) {
var err error
lockCfg, err = f.c.GetObjectLockConfiguration(ctx, &s3.GetObjectLockConfigurationInput{
Bucket: &lockBucket,
})
return f.shouldRetry(ctx, err)
})
if err != nil || lockCfg.ObjectLockConfiguration == nil ||
lockCfg.ObjectLockConfiguration.ObjectLockEnabled != types.ObjectLockEnabledEnabled {
_ = f.pacer.Call(func() (bool, error) {
_, err := f.c.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &lockBucket})
return f.shouldRetry(ctx, err)
})
t.Skipf("Object Lock not functional on this provider (GetObjectLockConfiguration: %v)", err)
}
// Switch f to use the Object Lock bucket for this test
oldBucket := f.rootBucket
oldRoot := f.root
oldRootDir := f.rootDirectory
f.rootBucket = lockBucket
f.root = lockBucket
f.rootDirectory = ""
defer func() {
f.rootBucket = oldBucket
f.root = oldRoot
f.rootDirectory = oldRootDir
}()
// Helper to remove an object with Object Lock protection
removeLocked := func(t *testing.T, obj fs.Object) {
t.Helper()
o := obj.(*Object)
// Remove legal hold if present
_ = o.setObjectLegalHold(ctx, types.ObjectLockLegalHoldStatusOff)
// Enable bypass governance retention for deletion
o.fs.opt.BypassGovernanceRetention = true
err := obj.Remove(ctx)
o.fs.opt.BypassGovernanceRetention = false
assert.NoError(t, err)
}
// Clean up the temporary bucket after all sub-tests
defer func() {
// List and remove all object versions
var objectVersions []types.ObjectIdentifier
listReq := &s3.ListObjectVersionsInput{Bucket: &lockBucket}
for {
var resp *s3.ListObjectVersionsOutput
err := f.pacer.Call(func() (bool, error) {
var err error
resp, err = f.c.ListObjectVersions(ctx, listReq)
return f.shouldRetry(ctx, err)
})
if err != nil {
t.Logf("Failed to list object versions for cleanup: %v", err)
break
}
for _, v := range resp.Versions {
objectVersions = append(objectVersions, types.ObjectIdentifier{
Key: v.Key,
VersionId: v.VersionId,
})
}
for _, m := range resp.DeleteMarkers {
objectVersions = append(objectVersions, types.ObjectIdentifier{
Key: m.Key,
VersionId: m.VersionId,
})
}
if !aws.ToBool(resp.IsTruncated) {
break
}
listReq.KeyMarker = resp.NextKeyMarker
listReq.VersionIdMarker = resp.NextVersionIdMarker
}
if len(objectVersions) > 0 {
bypass := true
_ = f.pacer.Call(func() (bool, error) {
_, err := f.c.DeleteObjects(ctx, &s3.DeleteObjectsInput{
Bucket: &lockBucket,
BypassGovernanceRetention: &bypass,
Delete: &types.Delete{
Objects: objectVersions,
Quiet: aws.Bool(true),
},
})
return f.shouldRetry(ctx, err)
})
}
_ = f.pacer.Call(func() (bool, error) {
_, err := f.c.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &lockBucket})
return f.shouldRetry(ctx, err)
})
}()
retainUntilDate := time.Now().UTC().Add(24 * time.Hour).Truncate(time.Second)
t.Run("Retention", func(t *testing.T) {
// Set Object Lock options for this test
f.opt.ObjectLockMode = "GOVERNANCE"
f.opt.ObjectLockRetainUntilDate = retainUntilDate.Format(time.RFC3339)
defer func() {
f.opt.ObjectLockMode = ""
f.opt.ObjectLockRetainUntilDate = ""
}()
// Upload an object with Object Lock retention
contents := random.String(100)
item := fstest.NewItem("test-object-lock-retention", contents, fstest.Time("2001-05-06T04:05:06.499999999Z"))
obj := fstests.PutTestContents(ctx, t, f, &item, contents, true)
defer func() {
removeLocked(t, obj)
}()
// Read back metadata and verify Object Lock settings
o := obj.(*Object)
gotMetadata, err := o.Metadata(ctx)
require.NoError(t, err)
assert.Equal(t, "GOVERNANCE", gotMetadata["object-lock-mode"])
gotRetainDate, err := time.Parse(time.RFC3339, gotMetadata["object-lock-retain-until-date"])
require.NoError(t, err)
assert.WithinDuration(t, retainUntilDate, gotRetainDate, time.Second)
})
t.Run("LegalHold", func(t *testing.T) {
// Set Object Lock legal hold option
f.opt.ObjectLockLegalHoldStatus = "ON"
defer func() {
f.opt.ObjectLockLegalHoldStatus = ""
}()
// Upload an object with legal hold
contents := random.String(100)
item := fstest.NewItem("test-object-lock-legal-hold", contents, fstest.Time("2001-05-06T04:05:06.499999999Z"))
obj := fstests.PutTestContents(ctx, t, f, &item, contents, true)
defer func() {
removeLocked(t, obj)
}()
// Verify legal hold is ON
o := obj.(*Object)
gotMetadata, err := o.Metadata(ctx)
require.NoError(t, err)
assert.Equal(t, "ON", gotMetadata["object-lock-legal-hold-status"])
// Set legal hold to OFF
err = o.setObjectLegalHold(ctx, types.ObjectLockLegalHoldStatusOff)
require.NoError(t, err)
// Clear cached metadata and re-read
o.meta = nil
gotMetadata, err = o.Metadata(ctx)
require.NoError(t, err)
assert.Equal(t, "OFF", gotMetadata["object-lock-legal-hold-status"])
})
t.Run("SetAfterUpload", func(t *testing.T) {
// Test the post-upload API path (PutObjectRetention + PutObjectLegalHold)
f.opt.ObjectLockSetAfterUpload = true
f.opt.ObjectLockMode = "GOVERNANCE"
f.opt.ObjectLockRetainUntilDate = retainUntilDate.Format(time.RFC3339)
f.opt.ObjectLockLegalHoldStatus = "ON"
defer func() {
f.opt.ObjectLockSetAfterUpload = false
f.opt.ObjectLockMode = ""
f.opt.ObjectLockRetainUntilDate = ""
f.opt.ObjectLockLegalHoldStatus = ""
}()
// Upload an object - lock applied AFTER upload via separate API calls
contents := random.String(100)
item := fstest.NewItem("test-object-lock-after-upload", contents, fstest.Time("2001-05-06T04:05:06.499999999Z"))
obj := fstests.PutTestContents(ctx, t, f, &item, contents, true)
defer func() {
removeLocked(t, obj)
}()
// Verify all Object Lock settings were applied
o := obj.(*Object)
gotMetadata, err := o.Metadata(ctx)
require.NoError(t, err)
assert.Equal(t, "GOVERNANCE", gotMetadata["object-lock-mode"])
gotRetainDate, err := time.Parse(time.RFC3339, gotMetadata["object-lock-retain-until-date"])
require.NoError(t, err)
assert.WithinDuration(t, retainUntilDate, gotRetainDate, time.Second)
assert.Equal(t, "ON", gotMetadata["object-lock-legal-hold-status"])
})
}
func (f *Fs) InternalTest(t *testing.T) { func (f *Fs) InternalTest(t *testing.T) {
t.Run("Metadata", f.InternalTestMetadata) t.Run("Metadata", f.InternalTestMetadata)
t.Run("NoHead", f.InternalTestNoHead) t.Run("NoHead", f.InternalTestNoHead)
t.Run("Versions", f.InternalTestVersions) t.Run("Versions", f.InternalTestVersions)
t.Run("ObjectLock", f.InternalTestObjectLock)
} }
var _ fstests.InternalTester = (*Fs)(nil) var _ fstests.InternalTester = (*Fs)(nil)

View File

@@ -5,6 +5,7 @@ import (
"context" "context"
"net/http" "net/http"
"testing" "testing"
"time"
"github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
@@ -92,3 +93,87 @@ var (
_ fstests.SetUploadCutoffer = (*Fs)(nil) _ fstests.SetUploadCutoffer = (*Fs)(nil)
_ fstests.SetCopyCutoffer = (*Fs)(nil) _ fstests.SetCopyCutoffer = (*Fs)(nil)
) )
func TestParseRetainUntilDate(t *testing.T) {
now := time.Now()
tests := []struct {
name string
input string
wantErr bool
checkFunc func(t *testing.T, result time.Time)
}{
{
name: "RFC3339 date",
input: "2030-01-15T10:30:00Z",
wantErr: false,
checkFunc: func(t *testing.T, result time.Time) {
expected, _ := time.Parse(time.RFC3339, "2030-01-15T10:30:00Z")
assert.Equal(t, expected, result)
},
},
{
name: "RFC3339 date with timezone",
input: "2030-06-15T10:30:00+02:00",
wantErr: false,
checkFunc: func(t *testing.T, result time.Time) {
expected, _ := time.Parse(time.RFC3339, "2030-06-15T10:30:00+02:00")
assert.Equal(t, expected, result)
},
},
{
name: "duration days",
input: "365d",
wantErr: false,
checkFunc: func(t *testing.T, result time.Time) {
expected := now.Add(365 * 24 * time.Hour)
diff := result.Sub(expected)
assert.Less(t, diff.Abs(), 2*time.Second, "result should be ~365 days from now")
},
},
{
name: "duration hours",
input: "24h",
wantErr: false,
checkFunc: func(t *testing.T, result time.Time) {
expected := now.Add(24 * time.Hour)
diff := result.Sub(expected)
assert.Less(t, diff.Abs(), 2*time.Second, "result should be ~24 hours from now")
},
},
{
name: "duration minutes",
input: "30m",
wantErr: false,
checkFunc: func(t *testing.T, result time.Time) {
expected := now.Add(30 * time.Minute)
diff := result.Sub(expected)
assert.Less(t, diff.Abs(), 2*time.Second, "result should be ~30 minutes from now")
},
},
{
name: "invalid input",
input: "not-a-date",
wantErr: true,
},
{
name: "empty input",
input: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := parseRetainUntilDate(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
if tt.checkFunc != nil {
tt.checkFunc(t, result)
}
})
}
}

View File

@@ -163,7 +163,7 @@ func (f *Fs) refreshJWTToken(ctx context.Context) (string, error) {
if err != nil { if err != nil {
return "", fmt.Errorf("invalid token received from server") return "", fmt.Errorf("invalid token received from server")
} }
var claims map[string]interface{} var claims map[string]any
if err := json.Unmarshal(payload, &claims); err != nil { if err := json.Unmarshal(payload, &claims); err != nil {
return "", err return "", err
} }
@@ -182,7 +182,7 @@ func (f *Fs) refreshJWTToken(ctx context.Context) (string, error) {
return f.token, nil return f.token, nil
} }
func (f *Fs) callAPI(ctx context.Context, method, path string, response interface{}) (*http.Response, error) { func (f *Fs) callAPI(ctx context.Context, method, path string, response any) (*http.Response, error) {
token, err := f.refreshJWTToken(ctx) token, err := f.refreshJWTToken(ctx)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -7,6 +7,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"maps"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
@@ -203,9 +204,7 @@ func (s *shadeChunkWriter) WriteChunk(ctx context.Context, chunkNumber int, read
var uploadRes *http.Response var uploadRes *http.Response
if len(partURL.Headers) > 0 { if len(partURL.Headers) > 0 {
opts.ExtraHeaders = make(map[string]string) opts.ExtraHeaders = make(map[string]string)
for k, v := range partURL.Headers { maps.Copy(opts.ExtraHeaders, partURL.Headers)
opts.ExtraHeaders[k] = v
}
} }
err = s.f.pacer.Call(func() (bool, error) { err = s.f.pacer.Call(func() (bool, error) {

View File

@@ -27,8 +27,8 @@ type Item struct {
FileCount int32 `json:"FileCount,omitempty"` FileCount int32 `json:"FileCount,omitempty"`
Name string `json:"Name,omitempty"` Name string `json:"Name,omitempty"`
FileName string `json:"FileName,omitempty"` FileName string `json:"FileName,omitempty"`
CreatedAt time.Time `json:"CreationDate,omitempty"` CreatedAt time.Time `json:"CreationDate"`
ModifiedAt time.Time `json:"ClientModifiedDate,omitempty"` ModifiedAt time.Time `json:"ClientModifiedDate"`
IsHidden bool `json:"IsHidden,omitempty"` IsHidden bool `json:"IsHidden,omitempty"`
Size int64 `json:"FileSizeBytes,omitempty"` Size int64 `json:"FileSizeBytes,omitempty"`
Type string `json:"odata.type,omitempty"` Type string `json:"odata.type,omitempty"`

View File

@@ -423,7 +423,7 @@ func (f *Fs) filePath(file string) string {
if f.opt.Enc != encoder.EncodeZero { if f.opt.Enc != encoder.EncodeZero {
subPath = f.opt.Enc.FromStandardPath(subPath) subPath = f.opt.Enc.FromStandardPath(subPath)
} }
return rest.URLPathEscape(subPath) return rest.URLPathEscapeAll(subPath)
} }
// dirPath returns a directory path (f.root, dir) // dirPath returns a directory path (f.root, dir)
@@ -610,7 +610,7 @@ func (f *Fs) findHeader(headers fs.CommaSepList, find string) bool {
// fetch the bearer token and set it if successful // fetch the bearer token and set it if successful
func (f *Fs) fetchAndSetBearerToken() error { func (f *Fs) fetchAndSetBearerToken() error {
_, err, _ := f.authSingleflight.Do("bearerToken", func() (interface{}, error) { _, err, _ := f.authSingleflight.Do("bearerToken", func() (any, error) {
if len(f.opt.BearerTokenCommand) == 0 { if len(f.opt.BearerTokenCommand) == 0 {
return nil, nil return nil, nil
} }

View File

@@ -80,3 +80,35 @@ func TestHeaders(t *testing.T) {
_, err := f.Features().About(context.Background()) _, err := f.Features().About(context.Background())
require.NoError(t, err) require.NoError(t, err)
} }
// TestReservedCharactersInPathAreEscaped verifies that reserved characters
// like semicolons and equals signs in file paths are percent-encoded in
// HTTP requests to the WebDAV server (RFC 3986 compliance).
func TestReservedCharactersInPathAreEscaped(t *testing.T) {
var capturedPath string
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
capturedPath = r.RequestURI
// Return a 404 so the NewObject call fails cleanly
w.WriteHeader(http.StatusNotFound)
})
ts := httptest.NewServer(handler)
defer ts.Close()
configfile.Install()
m := configmap.Simple{
"type": "webdav",
"url": ts.URL,
}
f, err := webdav.NewFs(context.Background(), remoteName, "", m)
require.NoError(t, err)
// Try to access a file with a semicolon in the name.
// We expect the request to fail (404), but the path should be escaped.
_, _ = f.NewObject(context.Background(), "my;test")
// The semicolon must be percent-encoded as %3B
assert.Contains(t, capturedPath, "my%3Btest", "semicolons in path should be percent-encoded")
assert.NotContains(t, capturedPath, "my;test", "raw semicolons should not appear in path")
}

View File

@@ -256,7 +256,7 @@ type WriteMultiMetadataRequest struct {
// WriteMetadata is used to write item metadata // WriteMetadata is used to write item metadata
type WriteMetadata struct { type WriteMetadata struct {
Attributes WriteAttributes `json:"attributes,omitempty"` Attributes WriteAttributes `json:"attributes"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Type string `json:"type"` Type string `json:"type"`
} }

View File

@@ -147,6 +147,16 @@ func ArchiveExtract(ctx context.Context, dst fs.Fs, dstDir string, src fs.Fs, sr
// extract files // extract files
err = ex.Extract(ctx, in, func(ctx context.Context, f archives.FileInfo) error { err = ex.Extract(ctx, in, func(ctx context.Context, f archives.FileInfo) error {
remote := f.NameInArchive remote := f.NameInArchive
// Strip leading "./" from archive paths. Tar files created with
// relative paths (e.g. "tar -czf archive.tar.gz .") use "./" prefixed
// entries. Without stripping, rclone encodes the "." as a full-width
// dot character creating a spurious directory. We only strip "./"
// specifically to avoid enabling path traversal attacks via "../".
remote = strings.TrimPrefix(remote, "./")
// If the entry was exactly "./" (the root dir), skip it
if remote == "" && f.IsDir() {
return nil
}
if dstDir != "" { if dstDir != "" {
remote = path.Join(dstDir, remote) remote = path.Join(dstDir, remote)
} }

View File

@@ -0,0 +1,62 @@
//go:build !plan9
package extract
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStripDotSlashPrefix(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "strip leading dot-slash from file",
input: "./file.txt",
expected: "file.txt",
},
{
name: "strip leading dot-slash from nested path",
input: "./subdir/file.txt",
expected: "subdir/file.txt",
},
{
name: "no prefix unchanged",
input: "file.txt",
expected: "file.txt",
},
{
name: "nested path unchanged",
input: "dir/file.txt",
expected: "dir/file.txt",
},
{
name: "dot-dot-slash NOT stripped (path traversal safety)",
input: "../etc/passwd",
expected: "../etc/passwd",
},
{
name: "dot-slash directory entry becomes empty",
input: "./",
expected: "",
},
{
name: "only single leading dot-slash stripped",
input: "././file.txt",
expected: "./file.txt",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// This mirrors the stripping logic in ArchiveExtract
got := strings.TrimPrefix(tc.input, "./")
assert.Equal(t, tc.expected, got)
})
}
}

View File

@@ -92,10 +92,10 @@ func TestCountWriterConcurrent(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(goroutines) wg.Add(goroutines)
for g := 0; g < goroutines; g++ { for range goroutines {
go func() { go func() {
defer wg.Done() defer wg.Done()
for i := 0; i < loops; i++ { for range loops {
n, err := cw.Write(data) n, err := cw.Write(data)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, chunkSize, n) assert.Equal(t, chunkSize, n)

View File

@@ -162,7 +162,7 @@ var commandDefinition = &cobra.Command{
Long: longHelp, Long: longHelp,
Annotations: map[string]string{ Annotations: map[string]string{
"versionIntroduced": "v1.58", "versionIntroduced": "v1.58",
"groups": "Filter,Copy,Important", "groups": "Filter,Copy,Important,Sync",
}, },
RunE: func(command *cobra.Command, args []string) error { RunE: func(command *cobra.Command, args []string) error {
// NOTE: avoid putting too much handling here, as it won't apply to the rc. // NOTE: avoid putting too much handling here, as it won't apply to the rc.

View File

@@ -128,9 +128,7 @@ func (b *bisyncRun) startLockRenewal() func() {
} }
stopLockRenewal := make(chan struct{}) stopLockRenewal := make(chan struct{})
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
ticker := time.NewTicker(time.Duration(b.opt.MaxLock) - time.Minute) ticker := time.NewTicker(time.Duration(b.opt.MaxLock) - time.Minute)
for { for {
select { select {
@@ -141,7 +139,7 @@ func (b *bisyncRun) startLockRenewal() func() {
return return
} }
} }
}() })
return func() { return func() {
close(stopLockRenewal) close(stopLockRenewal)
wg.Wait() wg.Wait()

View File

@@ -361,9 +361,7 @@ func StartStats() func() {
} }
stopStats := make(chan struct{}) stopStats := make(chan struct{})
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
ticker := time.NewTicker(*statsInterval) ticker := time.NewTicker(*statsInterval)
for { for {
select { select {
@@ -374,7 +372,7 @@ func StartStats() func() {
return return
} }
} }
}() })
return func() { return func() {
close(stopStats) close(stopStats)
wg.Wait() wg.Wait()

View File

@@ -108,7 +108,7 @@ func mountOptions(VFS *vfs.VFS, device string, mountpoint string, opt *mountlib.
func waitFor(fn func() bool) (ok bool) { func waitFor(fn func() bool) (ok bool) {
const totalWait = 10 * time.Second const totalWait = 10 * time.Second
const individualWait = 10 * time.Millisecond const individualWait = 10 * time.Millisecond
for i := 0; i < int(totalWait/individualWait); i++ { for range int(totalWait / individualWait) {
ok = fn() ok = fn()
if ok { if ok {
return ok return ok

View File

@@ -70,6 +70,15 @@ Note that |--stdout| and |--print-filename| are incompatible with |--urls|.
This will do |--transfers| copies in parallel. Note that if |--auto-filename| This will do |--transfers| copies in parallel. Note that if |--auto-filename|
is desired for all URLs then a file with only URLs and no filename can be used. is desired for all URLs then a file with only URLs and no filename can be used.
Each FILENAME in the CSV file can start with a relative path which will be appended
to the destination path provided at the command line. For example, running the command
shown above with the following CSV file will write two files to the destination:
|remote:dir/local/path/bar.json| and |remote:dir/another/local/directory/qux.json|
|||csv
https://example.org/foo/bar.json,local/path/bar.json
https://example.org/qux/baz.json,another/local/directory/qux.json
|||
### Troubleshooting ### Troubleshooting
If you can't get |rclone copyurl| to work then here are some things you can try: If you can't get |rclone copyurl| to work then here are some things you can try:

View File

@@ -41,9 +41,7 @@ func startProgress() func() {
} }
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
progressInterval := defaultProgressInterval progressInterval := defaultProgressInterval
if ShowStats() && *statsInterval > 0 { if ShowStats() && *statsInterval > 0 {
progressInterval = *statsInterval progressInterval = *statsInterval
@@ -65,7 +63,7 @@ func startProgress() func() {
return return
} }
} }
}() })
return func() { return func() {
close(stopStats) close(stopStats)
wg.Wait() wg.Wait()

View File

@@ -66,11 +66,9 @@ func testCacheCRUD(t *testing.T, h *Handler, c Cache, fileName string) {
func testCacheThrashDifferent(t *testing.T, h *Handler, c Cache) { func testCacheThrashDifferent(t *testing.T, h *Handler, c Cache) {
var wg sync.WaitGroup var wg sync.WaitGroup
for i := range 100 { for i := range 100 {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
testCacheCRUD(t, h, c, fmt.Sprintf("file-%d", i)) testCacheCRUD(t, h, c, fmt.Sprintf("file-%d", i))
}() })
} }
wg.Wait() wg.Wait()
} }
@@ -79,9 +77,7 @@ func testCacheThrashDifferent(t *testing.T, h *Handler, c Cache) {
func testCacheThrashSame(t *testing.T, h *Handler, c Cache) { func testCacheThrashSame(t *testing.T, h *Handler, c Cache) {
var wg sync.WaitGroup var wg sync.WaitGroup
for range 100 { for range 100 {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
// Write a handle // Write a handle
splitPath := []string{"file"} splitPath := []string{"file"}
@@ -108,7 +104,7 @@ func testCacheThrashSame(t *testing.T, h *Handler, c Cache) {
require.Error(t, err) require.Error(t, err)
assert.Equal(t, errStaleHandle, err) assert.Equal(t, errStaleHandle, err)
} }
}() })
} }
wg.Wait() wg.Wait()
} }

View File

@@ -133,6 +133,7 @@ WebDAV or S3, that work out of the box.)
{{< provider name="Dropbox" home="https://www.dropbox.com/" config="/dropbox/" >}} {{< provider name="Dropbox" home="https://www.dropbox.com/" config="/dropbox/" >}}
{{< provider name="Enterprise File Fabric" home="https://storagemadeeasy.com/about/" config="/filefabric/" >}} {{< provider name="Enterprise File Fabric" home="https://storagemadeeasy.com/about/" config="/filefabric/" >}}
{{< provider name="Exaba" home="https://exaba.com/" config="/s3/#exaba" >}} {{< provider name="Exaba" home="https://exaba.com/" config="/s3/#exaba" >}}
{{< provider name="Fastly Object Storage" home="https://www.fastly.com/products/storage" config="/s3/#fastly" >}}
{{< provider name="Fastmail Files" home="https://www.fastmail.com/" config="/webdav/#fastmail-files" >}} {{< provider name="Fastmail Files" home="https://www.fastmail.com/" config="/webdav/#fastmail-files" >}}
{{< provider name="FileLu Cloud Storage" home="https://filelu.com/" config="/filelu/" >}} {{< provider name="FileLu Cloud Storage" home="https://filelu.com/" config="/filelu/" >}}
{{< provider name="FileLu S5 (S3-Compatible Object Storage)" home="https://s5lu.com/" config="/s3/#filelu-s5" >}} {{< provider name="FileLu S5 (S3-Compatible Object Storage)" home="https://s5lu.com/" config="/s3/#filelu-s5" >}}
@@ -210,7 +211,6 @@ WebDAV or S3, that work out of the box.)
{{< provider name="Sia" home="https://sia.tech/" config="/sia/" >}} {{< provider name="Sia" home="https://sia.tech/" config="/sia/" >}}
{{< provider name="SMB / CIFS" home="https://en.wikipedia.org/wiki/Server_Message_Block" config="/smb/" >}} {{< provider name="SMB / CIFS" home="https://en.wikipedia.org/wiki/Server_Message_Block" config="/smb/" >}}
{{< provider name="Spectra Logic" home="https://spectralogic.com/blackpearl-nearline-object-gateway/" config="/s3/#spectralogic" >}} {{< provider name="Spectra Logic" home="https://spectralogic.com/blackpearl-nearline-object-gateway/" config="/s3/#spectralogic" >}}
{{< provider name="StackPath" home="https://www.stackpath.com/products/object-storage/" config="/s3/#stackpath" >}}
{{< provider name="Storj" home="https://storj.io/" config="/storj/" >}} {{< provider name="Storj" home="https://storj.io/" config="/storj/" >}}
{{< provider name="Synology" home="https://c2.synology.com/en-global/object-storage/overview" config="/s3/#synology-c2" >}} {{< provider name="Synology" home="https://c2.synology.com/en-global/object-storage/overview" config="/s3/#synology-c2" >}}
{{< provider name="SugarSync" home="https://sugarsync.com/" config="/sugarsync/" >}} {{< provider name="SugarSync" home="https://sugarsync.com/" config="/sugarsync/" >}}
@@ -219,6 +219,7 @@ WebDAV or S3, that work out of the box.)
{{< provider name="Wasabi" home="https://wasabi.com/" config="/s3/#wasabi" >}} {{< provider name="Wasabi" home="https://wasabi.com/" config="/s3/#wasabi" >}}
{{< provider name="WebDAV" home="https://en.wikipedia.org/wiki/WebDAV" config="/webdav/" >}} {{< provider name="WebDAV" home="https://en.wikipedia.org/wiki/WebDAV" config="/webdav/" >}}
{{< provider name="Yandex Disk" home="https://disk.yandex.com/" config="/yandex/" >}} {{< provider name="Yandex Disk" home="https://disk.yandex.com/" config="/yandex/" >}}
{{< provider name="Zadara Object Storage" home="https://www.zadara.com" config="/s3/#zadara" >}}
{{< provider name="Zoho WorkDrive" home="https://www.zoho.com/workdrive/" config="/zoho/" >}} {{< provider name="Zoho WorkDrive" home="https://www.zoho.com/workdrive/" config="/zoho/" >}}
{{< provider name="Zata" home="https://zata.ai/" config="/s3/#Zata" end="true" >}} {{< provider name="Zata" home="https://zata.ai/" config="/s3/#Zata" end="true" >}}
{{< provider name="The local filesystem" home="/local/" config="/local/" end="true">}} {{< provider name="The local filesystem" home="/local/" config="/local/" end="true">}}

View File

@@ -978,7 +978,7 @@ put them back in again. -->
- Nathanael Demacon <7271496+quantumsheep@users.noreply.github.com> - Nathanael Demacon <7271496+quantumsheep@users.noreply.github.com>
- ahxxm <ahxxm@users.noreply.github.com> - ahxxm <ahxxm@users.noreply.github.com>
- Flora Thiebaut <johann.thiebaut@gmail.com> - Flora Thiebaut <johann.thiebaut@gmail.com>
- kingston125 <support@filelu.com> - kingston125 <support@filelu.com> <kingston125@github.com>
- Ser-Bul <30335009+Ser-Bul@users.noreply.github.com> - Ser-Bul <30335009+Ser-Bul@users.noreply.github.com>
- jinjingroad <jinjingroad@sina.com> - jinjingroad <jinjingroad@sina.com>
- necaran <55765083+necaran@users.noreply.github.com> - necaran <55765083+necaran@users.noreply.github.com>
@@ -1073,3 +1073,12 @@ put them back in again. -->
- jzunigax2 <125698953+jzunigax2@users.noreply.github.com> - jzunigax2 <125698953+jzunigax2@users.noreply.github.com>
- lullius <lullius@users.noreply.github.com> - lullius <lullius@users.noreply.github.com>
- StarHack <StarHack@users.noreply.github.com> - StarHack <StarHack@users.noreply.github.com>
- Leon Brocard <acme@astray.com>
- Cohinem <143964778+Cohinem@users.noreply.github.com>
- Jack Kelly <jack@OpenClimateFix.org>
- Prakhar Chhalotre <chhalotreprakhar00@gmail.com>
- Varun Chawla <34209028+veeceey@users.noreply.github.com>
- Jan-Philipp Reßler <75355263+TabError@users.noreply.github.com>
- Shlomi Avihou <shlomi@zadarastorage.com>
- Chris <238498929+chris081519-crypto@users.noreply.github.com>
- Jan-Philipp Reßler <xodarap@xodarap.de>

View File

@@ -6,6 +6,33 @@ description: "Rclone Changelog"
# Changelog # Changelog
## v1.73.1 - 2026-02-17
[See commits](https://github.com/rclone/rclone/compare/v1.73.0...v1.73.1)
- Bug Fixes
- accounting: Fix missing server side stats from core/stats rc (Nick Craig-Wood)
- build
- Fix CVE-2025-68121 by updating go to 1.25.7 or later (Nick Craig-Wood)
- Bump github.com/go-chi/chi/v5 from 5.2.3 to 5.2.5 to fix GO-2026-4316 (albertony)
- docs: Extend copyurl docs with an example of CSV FILENAMEs starting with a path. (Jack Kelly)
- march: Fix runtime: program exceeds 10000-thread limit (Nick Craig-Wood)
- pacer
- Fix deadlock between pacer token and --max-connections (Nick Craig-Wood)
- Re-read the sleep time as it may be stale (Nick Craig-Wood)
- Drime
- Fix files and directories being created in the default workspace (Nick Craig-Wood)
- Filelu
- Avoid buffering entire file in memory (kingston125)
- Add multipart upload support with configurable cutoff (kingston125)
- Filen
- Fix 32 bit targets not being able to list directories (Enduriel)
- Fix potential panic in case of error during upload (Enduriel)
- Internxt
- Implement re-login under refresh logic, improve retry logic (José Zúniga)
-S3
- Set list_version to 2 for FileLu S3 configuration (kingston125)
## v1.73.0 - 2026-01-30 ## v1.73.0 - 2026-01-30
[See commits](https://github.com/rclone/rclone/compare/v1.72.0...v1.73.0) [See commits](https://github.com/rclone/rclone/compare/v1.72.0...v1.73.0)

View File

@@ -380,7 +380,7 @@ not the rclone developers so it may be out of date. Its current version is as be
## Source installation {#source} ## Source installation {#source}
Make sure you have git and [Go](https://golang.org/) installed. Make sure you have git and [Go](https://golang.org/) installed.
Go version 1.24 or newer is required, the latest release is recommended. Go version 1.25 or newer is required, the latest release is recommended.
You can get it from your package manager, or download it from You can get it from your package manager, or download it from
[golang.org/dl](https://golang.org/dl/). Then you can run the following: [golang.org/dl](https://golang.org/dl/). Then you can run the following:

View File

@@ -23,6 +23,7 @@ The S3 backend can be used with a number of different providers:
{{< provider name="DigitalOcean Spaces" home="https://www.digitalocean.com/products/object-storage/" config="/s3/#digitalocean-spaces" >}} {{< provider name="DigitalOcean Spaces" home="https://www.digitalocean.com/products/object-storage/" config="/s3/#digitalocean-spaces" >}}
{{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}} {{< provider name="Dreamhost" home="https://www.dreamhost.com/cloud/storage/" config="/s3/#dreamhost" >}}
{{< provider name="Exaba" home="https://exaba.com/" config="/s3/#exaba" >}} {{< provider name="Exaba" home="https://exaba.com/" config="/s3/#exaba" >}}
{{< provider name="Fastly Object Storage" home="https://www.fastly.com/products/storage" config="/s3/#fastly" >}}
{{< provider name="FileLu S5 (S3-Compatible Object Storage)" home="https://s5lu.com/" config="/s3/#filelu-s5" >}} {{< provider name="FileLu S5 (S3-Compatible Object Storage)" home="https://s5lu.com/" config="/s3/#filelu-s5" >}}
{{< provider name="GCS" home="https://cloud.google.com/storage/docs" config="/s3/#google-cloud-storage" >}} {{< provider name="GCS" home="https://cloud.google.com/storage/docs" config="/s3/#google-cloud-storage" >}}
{{< provider name="Hetzner" home="https://www.hetzner.com/storage/object-storage/" config="/s3/#hetzner" >}} {{< provider name="Hetzner" home="https://www.hetzner.com/storage/object-storage/" config="/s3/#hetzner" >}}
@@ -51,11 +52,11 @@ The S3 backend can be used with a number of different providers:
{{< provider name="Selectel" home="https://selectel.ru/services/cloud/storage/" config="/s3/#selectel" >}} {{< provider name="Selectel" home="https://selectel.ru/services/cloud/storage/" config="/s3/#selectel" >}}
{{< provider name="Servercore Object Storage" home="https://servercore.com/services/object-storage/" config="/s3/#servercore" >}} {{< provider name="Servercore Object Storage" home="https://servercore.com/services/object-storage/" config="/s3/#servercore" >}}
{{< provider name="Spectra Logic" home="https://spectralogic.com/blackpearl-nearline-object-gateway" config="/s3/#spectralogic" >}} {{< provider name="Spectra Logic" home="https://spectralogic.com/blackpearl-nearline-object-gateway" config="/s3/#spectralogic" >}}
{{< provider name="StackPath" home="https://www.stackpath.com/products/object-storage/" config="/s3/#stackpath" >}}
{{< provider name="Storj" home="https://storj.io/" config="/s3/#storj" >}} {{< provider name="Storj" home="https://storj.io/" config="/s3/#storj" >}}
{{< provider name="Synology C2 Object Storage" home="https://c2.synology.com/en-global/object-storage/overview" config="/s3/#synology-c2" >}} {{< provider name="Synology C2 Object Storage" home="https://c2.synology.com/en-global/object-storage/overview" config="/s3/#synology-c2" >}}
{{< provider name="Tencent Cloud Object Storage (COS)" home="https://intl.cloud.tencent.com/product/cos" config="/s3/#tencent-cos" >}} {{< provider name="Tencent Cloud Object Storage (COS)" home="https://intl.cloud.tencent.com/product/cos" config="/s3/#tencent-cos" >}}
{{< provider name="Wasabi" home="https://wasabi.com/" config="/s3/#wasabi" end="true" >}} {{< provider name="Wasabi" home="https://wasabi.com/" config="/s3/#wasabi" >}}
{{< provider name="Zadara Object Storage" home="https://www.zadara.com" config="/s3/#zadara" >}}
{{< provider name="Zata" home="https://zata.ai/" config="/s3/#Zata" end="true" >}} {{< provider name="Zata" home="https://zata.ai/" config="/s3/#Zata" end="true" >}}
{{< /provider_list >}} {{< /provider_list >}}
@@ -903,10 +904,52 @@ section, small files that are not uploaded as multipart, use a different tag, ca
the upload to fail. A simple solution is to set the `--s3-upload-cutoff 0` and force the upload to fail. A simple solution is to set the `--s3-upload-cutoff 0` and force
all the files to be uploaded as multipart. all the files to be uploaded as multipart.
#### Setting Object Lock retention
Rclone supports setting Object Lock retention on uploaded objects with
the following options:
- `--s3-object-lock-mode` - Set the Object Lock mode (GOVERNANCE or COMPLIANCE)
- `--s3-object-lock-retain-until-date` - Set the retention date (RFC3339 format or duration like `365d`, `24h`)
- `--s3-object-lock-legal-hold-status` - Set legal hold (ON or OFF)
Example - upload files with 1 year GOVERNANCE retention:
rclone copy local:/data remote:bucket \
--s3-object-lock-mode GOVERNANCE \
--s3-object-lock-retain-until-date 365d
#### Copying Object Lock settings from source
When using `--metadata`, you can use the special value `copy` to preserve
the source object's Object Lock settings:
rclone copy source:bucket dest:bucket \
--metadata \
--s3-object-lock-mode copy \
--s3-object-lock-retain-until-date copy
You can also mix copied and explicit values. For example, to change the
mode from COMPLIANCE to GOVERNANCE while preserving the original retention date:
rclone copy source:bucket dest:bucket \
--metadata \
--s3-object-lock-mode GOVERNANCE \
--s3-object-lock-retain-until-date copy
#### Additional Object Lock options
- `--s3-bypass-governance-retention` - Required to delete or overwrite objects
with GOVERNANCE mode retention before the retention date expires
- `--s3-bucket-object-lock-enabled` - Enable Object Lock when creating a new
bucket with `rclone mkdir`
- `--s3-object-lock-set-after-upload` - Set Object Lock via separate API calls
after upload (for providers that don't support Object Lock headers during PUT)
<!-- autogenerated options start - DO NOT EDIT - instead edit fs.RegInfo in backend/s3/s3.go and run make backenddocs to verify --> <!-- markdownlint-disable-line line-length --> <!-- autogenerated options start - DO NOT EDIT - instead edit fs.RegInfo in backend/s3/s3.go and run make backenddocs to verify --> <!-- markdownlint-disable-line line-length -->
### Standard options ### Standard options
Here are the Standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, BizflyCloud, Ceph, ChinaMobile, Cloudflare, Cubbit, DigitalOcean, Dreamhost, Exaba, FileLu, FlashBlade, GCS, Hetzner, HuaweiOBS, IBMCOS, IDrive, Intercolo, IONOS, Leviia, Liara, Linode, LyveCloud, Magalu, Mega, Minio, Netease, Outscale, OVHcloud, Petabox, Qiniu, Rabata, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, Servercore, SpectraLogic, StackPath, Storj, Synology, TencentCOS, Wasabi, Zata, Other). Here are the Standard options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, BizflyCloud, Ceph, ChinaMobile, Cloudflare, Cubbit, DigitalOcean, Dreamhost, Exaba, FileLu, FlashBlade, GCS, Hetzner, HuaweiOBS, IBMCOS, IDrive, Intercolo, IONOS, Leviia, Liara, Linode, LyveCloud, Magalu, Mega, Minio, Netease, Outscale, OVHcloud, Petabox, Qiniu, Rabata, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, Servercore, SpectraLogic, Storj, Synology, TencentCOS, Wasabi, Zata, Other).
#### --s3-provider #### --s3-provider
@@ -999,8 +1042,6 @@ Properties:
- Servercore Object Storage - Servercore Object Storage
- "SpectraLogic" - "SpectraLogic"
- Spectra Logic Black Pearl - Spectra Logic Black Pearl
- "StackPath"
- StackPath Object Storage
- "Storj" - "Storj"
- Storj (S3 Compatible Gateway) - Storj (S3 Compatible Gateway)
- "Synology" - "Synology"
@@ -1068,7 +1109,7 @@ Properties:
- Config: region - Config: region
- Env Var: RCLONE_S3_REGION - Env Var: RCLONE_S3_REGION
- Provider: AWS,BizflyCloud,Ceph,Cloudflare,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IBMCOS,Intercolo,IONOS,Leviia,LyveCloud,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,Rabata,RackCorp,Scaleway,SeaweedFS,Selectel,Servercore,StackPath,Synology,Wasabi,Zata,Other - Provider: AWS,BizflyCloud,Ceph,Cloudflare,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IBMCOS,Intercolo,IONOS,Leviia,LyveCloud,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,Rabata,RackCorp,Scaleway,SeaweedFS,Selectel,Servercore,Synology,Wasabi,Zata,Other
- Type: string - Type: string
- Required: false - Required: false
- Examples: - Examples:
@@ -1186,11 +1227,11 @@ Properties:
- "" - ""
- Use this if unsure. - Use this if unsure.
- Will use v4 signatures and an empty region. - Will use v4 signatures and an empty region.
- Provider: Ceph,DigitalOcean,Dreamhost,Exaba,GCS,IBMCOS,Leviia,LyveCloud,Minio,Netease,SeaweedFS,StackPath,Wasabi,Other - Provider: Ceph,DigitalOcean,Dreamhost,Exaba,GCS,IBMCOS,Leviia,LyveCloud,Minio,Netease,SeaweedFS,Wasabi,Other
- "other-v2-signature" - "other-v2-signature"
- Use this only if v4 signatures don't work. - Use this only if v4 signatures don't work.
- E.g. pre Jewel/v10 CEPH. - E.g. pre Jewel/v10 CEPH.
- Provider: Ceph,DigitalOcean,Dreamhost,Exaba,GCS,IBMCOS,Leviia,LyveCloud,Minio,Netease,SeaweedFS,StackPath,Wasabi,Other - Provider: Ceph,DigitalOcean,Dreamhost,Exaba,GCS,IBMCOS,Leviia,LyveCloud,Minio,Netease,SeaweedFS,Wasabi,Other
- "auto" - "auto"
- R2 buckets are automatically distributed across Cloudflare's data centers for low latency. - R2 buckets are automatically distributed across Cloudflare's data centers for low latency.
- Provider: Cloudflare - Provider: Cloudflare
@@ -1504,7 +1545,7 @@ Properties:
- Config: endpoint - Config: endpoint
- Env Var: RCLONE_S3_ENDPOINT - Env Var: RCLONE_S3_ENDPOINT
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cloudflare,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,FlashBlade,GCS,Hetzner,HuaweiOBS,IBMCOS,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Mega,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,Rabata,RackCorp,Rclone,Scaleway,SeaweedFS,Selectel,Servercore,SpectraLogic,StackPath,Storj,Synology,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cloudflare,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,FlashBlade,GCS,Hetzner,HuaweiOBS,IBMCOS,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Mega,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,Rabata,RackCorp,Rclone,Scaleway,SeaweedFS,Selectel,Servercore,SpectraLogic,Storj,Synology,TencentCOS,Wasabi,Zata,Other
- Type: string - Type: string
- Required: false - Required: false
- Examples: - Examples:
@@ -2292,15 +2333,6 @@ Properties:
- "s3.kz-1.srvstorage.kz" - "s3.kz-1.srvstorage.kz"
- Almaty, Kazakhstan - Almaty, Kazakhstan
- Provider: Servercore - Provider: Servercore
- "s3.us-east-2.stackpathstorage.com"
- US East Endpoint
- Provider: StackPath
- "s3.us-west-1.stackpathstorage.com"
- US West Endpoint
- Provider: StackPath
- "s3.eu-central-1.stackpathstorage.com"
- EU Endpoint
- Provider: StackPath
- "gateway.storjshare.io" - "gateway.storjshare.io"
- Global Hosted Gateway - Global Hosted Gateway
- Provider: Storj - Provider: Storj
@@ -2810,36 +2842,36 @@ Properties:
- Config: acl - Config: acl
- Env Var: RCLONE_S3_ACL - Env Var: RCLONE_S3_ACL
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IBMCOS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,StackPath,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IBMCOS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,TencentCOS,Wasabi,Zata,Other
- Type: string - Type: string
- Required: false - Required: false
- Examples: - Examples:
- "private" - "private"
- Owner gets FULL_CONTROL. - Owner gets FULL_CONTROL.
- No one else has access rights (default). - No one else has access rights (default).
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,StackPath,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,Wasabi,Zata,Other
- "public-read" - "public-read"
- Owner gets FULL_CONTROL. - Owner gets FULL_CONTROL.
- The AllUsers group gets READ access. - The AllUsers group gets READ access.
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,StackPath,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,TencentCOS,Wasabi,Zata,Other
- "public-read-write" - "public-read-write"
- Owner gets FULL_CONTROL. - Owner gets FULL_CONTROL.
- The AllUsers group gets READ and WRITE access. - The AllUsers group gets READ and WRITE access.
- Granting this on a bucket is generally not recommended. - Granting this on a bucket is generally not recommended.
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,StackPath,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,TencentCOS,Wasabi,Zata,Other
- "authenticated-read" - "authenticated-read"
- Owner gets FULL_CONTROL. - Owner gets FULL_CONTROL.
- The AuthenticatedUsers group gets READ access. - The AuthenticatedUsers group gets READ access.
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,StackPath,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,TencentCOS,Wasabi,Zata,Other
- "bucket-owner-read" - "bucket-owner-read"
- Object owner gets FULL_CONTROL. - Object owner gets FULL_CONTROL.
- Bucket owner gets READ access. - Bucket owner gets READ access.
- If you specify this canned ACL when creating a bucket, Amazon S3 ignores it. - If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,StackPath,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,TencentCOS,Wasabi,Zata,Other
- "bucket-owner-full-control" - "bucket-owner-full-control"
- Both the object owner and the bucket owner get FULL_CONTROL over the object. - Both the object owner and the bucket owner get FULL_CONTROL over the object.
- If you specify this canned ACL when creating a bucket, Amazon S3 ignores it. - If you specify this canned ACL when creating a bucket, Amazon S3 ignores it.
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,StackPath,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,TencentCOS,Wasabi,Zata,Other
- "private" - "private"
- Owner gets FULL_CONTROL. - Owner gets FULL_CONTROL.
- No one else has access rights (default). - No one else has access rights (default).
@@ -3004,7 +3036,7 @@ Properties:
### Advanced options ### Advanced options
Here are the Advanced options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, BizflyCloud, Ceph, ChinaMobile, Cloudflare, Cubbit, DigitalOcean, Dreamhost, Exaba, FileLu, FlashBlade, GCS, Hetzner, HuaweiOBS, IBMCOS, IDrive, Intercolo, IONOS, Leviia, Liara, Linode, LyveCloud, Magalu, Mega, Minio, Netease, Outscale, OVHcloud, Petabox, Qiniu, Rabata, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, Servercore, SpectraLogic, StackPath, Storj, Synology, TencentCOS, Wasabi, Zata, Other). Here are the Advanced options specific to s3 (Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, BizflyCloud, Ceph, ChinaMobile, Cloudflare, Cubbit, DigitalOcean, Dreamhost, Exaba, FileLu, FlashBlade, GCS, Hetzner, HuaweiOBS, IBMCOS, IDrive, Intercolo, IONOS, Leviia, Liara, Linode, LyveCloud, Magalu, Mega, Minio, Netease, Outscale, OVHcloud, Petabox, Qiniu, Rabata, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, Servercore, SpectraLogic, Storj, Synology, TencentCOS, Wasabi, Zata, Other).
#### --s3-bucket-acl #### --s3-bucket-acl
@@ -3023,7 +3055,7 @@ Properties:
- Config: bucket_acl - Config: bucket_acl
- Env Var: RCLONE_S3_BUCKET_ACL - Env Var: RCLONE_S3_BUCKET_ACL
- Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IBMCOS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Mega,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,Servercore,StackPath,TencentCOS,Wasabi,Zata,Other - Provider: AWS,Alibaba,ArvanCloud,BizflyCloud,Ceph,ChinaMobile,Cubbit,DigitalOcean,Dreamhost,Exaba,FileLu,GCS,Hetzner,HuaweiOBS,IBMCOS,IDrive,Intercolo,IONOS,Leviia,Liara,Linode,LyveCloud,Magalu,Mega,Minio,Netease,Outscale,OVHcloud,Petabox,Qiniu,RackCorp,Scaleway,SeaweedFS,Servercore,TencentCOS,Wasabi,Zata,Other
- Type: string - Type: string
- Required: false - Required: false
- Examples: - Examples:
@@ -4992,7 +5024,7 @@ Option Storage.
Type of storage to configure. Type of storage to configure.
Choose a number from below, or type in your own value. Choose a number from below, or type in your own value.
... ...
XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, DigitalOcean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Magalu, Netease, RackCorp, Scaleway, SeaweedFS, StackPath, Storj, Synology, Tencent COS and Wasabi XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, China Mobile, Cloudflare, ArvanCloud, DigitalOcean, Dreamhost, Huawei OBS, IBM COS, Lyve Cloud, Minio, Magalu, Netease, RackCorp, Scaleway, SeaweedFS, Storj, Synology, Tencent COS and Wasabi
\ (s3) \ (s3)
... ...
Storage> s3 Storage> s3
@@ -5279,6 +5311,92 @@ secret_access_key = XXX
endpoint = http://127.0.0.1:9000/ endpoint = http://127.0.0.1:9000/
``` ```
### Fastly Object Storage {#fastly}
[Fastly Object Storage](https://www.fastly.com/products/storage) is an
S3-compatible object storage service from Fastly. It provides three
regions (US East, US West, and EU Central) with mandatory server-side
encryption.
Here is an example of making a configuration. First run:
```console
rclone config
```
This will guide you through an interactive setup process.
```text
No remotes found, make a new one?
n) New remote
s) Set configuration password
q) Quit config
n/s/q> n
Enter name for new remote.
name> fastly
Option Storage.
Type of storage to configure.
Storage> s3
Option provider.
Choose your S3 provider.
provider> Fastly
Option env_auth.
Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
Only applies if access_key_id and secret_access_key is blank.
env_auth> false
Option access_key_id.
AWS Access Key ID.
access_key_id> YOUR_ACCESS_KEY
Option secret_access_key.
AWS Secret Access Key (password).
secret_access_key> YOUR_SECRET_KEY
Option region.
Region where your bucket will be created and your data stored.
region> us-east
Option endpoint.
Endpoint for S3 API.
endpoint> us-east.object.fastlystorage.app
Edit advanced config?
y) Yes
n) No (default)
y/n> n
Configuration complete.
Options:
- type: s3
- provider: Fastly
- access_key_id: YOUR_ACCESS_KEY
- secret_access_key: YOUR_SECRET_KEY
- region: us-east
- endpoint: us-east.object.fastlystorage.app
Keep this "fastly" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> y
```
The resulting configuration file should look like:
```ini
[fastly]
type = s3
provider = Fastly
access_key_id = YOUR_ACCESS_KEY
secret_access_key = YOUR_SECRET_KEY
region = us-east
endpoint = us-east.object.fastlystorage.app
```
### Google Cloud Storage ### Google Cloud Storage
[GoogleCloudStorage](https://cloud.google.com/storage/docs) is an [GoogleCloudStorage](https://cloud.google.com/storage/docs) is an
@@ -5330,7 +5448,7 @@ Option Storage.
Type of storage to configure. Type of storage to configure.
Choose a number from below, or type in your own value. Choose a number from below, or type in your own value.
[snip] [snip]
XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, GCS, Hetzner, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Minio, Netease, Outscale, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, StackPath, Storj, Synology, TencentCOS, Wasabi, Qiniu and others XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, GCS, Hetzner, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Minio, Netease, Outscale, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, Storj, Synology, TencentCOS, Wasabi, Qiniu and others
\ (s3) \ (s3)
[snip] [snip]
Storage> s3 Storage> s3
@@ -7112,7 +7230,7 @@ Option Storage.
Type of storage to configure. Type of storage to configure.
Choose a number from below, or type in your own value. Choose a number from below, or type in your own value.
[...] [...]
XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, GCS, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Minio, Netease, Outscale, OVHcloud, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, StackPath, Storj, Synology, TencentCOS, Wasabi, Qiniu and others XX / Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, GCS, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Minio, Netease, Outscale, OVHcloud, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, Storj, Synology, TencentCOS, Wasabi, Qiniu and others
\ (s3) \ (s3)
[...] [...]
Storage> s3 Storage> s3
@@ -7486,7 +7604,7 @@ Option Storage.
Type of storage to configure. Type of storage to configure.
Choose a number from below, or type in your own value. Choose a number from below, or type in your own value.
[snip] [snip]
4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, FlashBlade, GCS, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Minio, Netease, Outscale, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, StackPath, Storj, Synology, TencentCOS, Wasabi, Qiniu and others 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, ArvanCloud, Ceph, ChinaMobile, Cloudflare, DigitalOcean, Dreamhost, FlashBlade, GCS, HuaweiOBS, IBMCOS, IDrive, IONOS, LyveCloud, Leviia, Liara, Linode, Magalu, Minio, Netease, Outscale, Petabox, RackCorp, Rclone, Scaleway, SeaweedFS, Selectel, Storj, Synology, TencentCOS, Wasabi, Qiniu and others
\ (s3) \ (s3)
[snip] [snip]
Storage> s3 Storage> s3
@@ -9099,6 +9217,125 @@ server_side_encryption =
storage_class = storage_class =
``` ```
### Zadara Object Storage {#zadara}
[Zadara Object Storage](https://www.zadara.com) is a fully-managed,
enterprise-grade, S3-compatible storage solution that provides scalable,
multi-tenant object storage with flexible deployment options (on-prem,
hybrid, or cloud).
Zadara Object Storage can be configured using `rclone config` with `s3` as
the type and `Zadara` as the provider name. Here is an example
run of the configurator.
Authentication and endpoint information, including the region field,
should be fetched from Zadaras Object Storage management interface
To configure access to Zadara Object Storage, first run:
```console
rclone config
```
```text
This will guide you through an interactive setup process:
No remotes found, make a new one\?
n) New remote
s) Set configuration password
n/s> n
Enter name for new remote.
name> Zadara-Object-Storage
Option Storage.
Type of storage to configure.
Choose a number from below, or type in your own value.
XX / Amazon S3 Compliant Storage Providers including AWS ...
\ (s3)
Storage> s3
Option provider.
Choose your S3 provider.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
XX / Zadara Object Storage
\ (Zadara)
provider> Zadara
Option env_auth.
Get AWS credentials from runtime (environment variables or EC2/ECS meta data if no env vars).
Only applies if access_key_id and secret_access_key is blank.
Choose a number from below, or type in your own boolean value (true or false).
Press Enter for the default (false).
1 / Enter AWS credentials in the next step.
\ (false)
2 / Get AWS credentials from the environment (env vars or IAM).
\ (true)
env_auth>
Option access_key_id.
AWS Access Key ID.
Leave blank for anonymous access or runtime credentials.
Enter a value. Press Enter to leave empty.
access_key_id> S3_ACCESS_KEY
Option secret_access_key.
AWS Secret Access Key (password).
Leave blank for anonymous access or runtime credentials.
Enter a value. Press Enter to leave empty.
secret_access_key> S3_SECRET_KEY
Option region.
Region to connect to.
Leave blank if you are using an S3 clone and you don't have a region.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
/ The default region.
1 | Leave location constraint empty.
\ (us-east-1)
region>
Option endpoint.
Endpoint for S3 API.
Required when using an S3 clone.
Enter a value. Press Enter to leave empty.
endpoint> https://vsa-00000001-public-zadara-cloud-01.zadarazios.com
Edit advanced config?
y) Yes
n) No (default)
y/n>
Configuration complete.
Options:
- type: s3
- provider: Zadara
- access_key_id: S3_ACCESS_KEY
- secret_access_key: S3_SECRET_KEY
- endpoint: https://vsa-00000001-public-zadara-cloud-01.zadarazios.com
Keep this "Zadara-Object-Storage" remote?
y) Yes this is OK (default)
e) Edit this remote
d) Delete this remote
y/e/d> y
```
This will leave the config file looking like this.
```ini
[Zadara-Object-Storage]
type = s3
provider = Zadara
access_key_id = S3_ACCESS_KEY
secret_access_key = S3_SECRET_KEY
endpoint = https://vsa-00000001-public-zadara-cloud-01.zadarazios.com
```
### Zata Object Storage {#Zata} ### Zata Object Storage {#Zata}
[Zata Object Storage](https://zata.ai/) provides a secure, S3-compatible cloud [Zata Object Storage](https://zata.ai/) provides a secure, S3-compatible cloud

View File

@@ -16,16 +16,18 @@ Thank you to our sponsors:
{{< sponsor src="/img/logos/rabata.svg" width="300" height="200" title="Visit our sponsor Rabata.io" link="https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general">}} {{< sponsor src="/img/logos/rabata.svg" width="300" height="200" title="Visit our sponsor Rabata.io" link="https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general">}}
{{< sponsor src="/img/logos/idrive_e2.svg" width="300" height="200" title="Visit our sponsor IDrive e2" link="https://www.idrive.com/e2/?refer=rclone">}} {{< sponsor src="/img/logos/idrive_e2.svg" width="300" height="200" title="Visit our sponsor IDrive e2" link="https://www.idrive.com/e2/?refer=rclone">}}
{{< sponsor src="/img/logos/filescom-enterprise-grade-workflows.png" width="300" height="200" title="Start Your Free Trial Today" link="https://files.com/?utm_source=rclone&utm_medium=referral&utm_campaign=banner&utm_term=rclone">}} {{< sponsor src="/img/logos/filescom-enterprise-grade-workflows.png" width="300" height="200" title="Start Your Free Trial Today" link="https://files.com/?utm_source=rclone&utm_medium=referral&utm_campaign=banner&utm_term=rclone">}}
{{< sponsor src="/img/logos/internxt.jpg" width="300" height="200" title="Visit rclone's sponsor Internxt" link="https://internxt.com/specialoffer/rclone">}}
{{< sponsor src="/img/logos/mega-s4.svg" width="300" height="200" title="MEGA S4: New S3 compatible object storage. High scale. Low cost. Free egress." link="https://mega.io/objectstorage?utm_source=rclone&utm_medium=referral&utm_campaign=rclone-mega-s4&mct=rclonepromo">}} {{< sponsor src="/img/logos/mega-s4.svg" width="300" height="200" title="MEGA S4: New S3 compatible object storage. High scale. Low cost. Free egress." link="https://mega.io/objectstorage?utm_source=rclone&utm_medium=referral&utm_campaign=rclone-mega-s4&mct=rclonepromo">}}
{{< sponsor src="/img/logos/sia.svg" width="200" height="200" title="Visit our sponsor sia" link="https://sia.tech">}} {{< sponsor src="/img/logos/sia.svg" width="200" height="200" title="Visit our sponsor sia" link="https://sia.tech">}}
{{< sponsor src="/img/logos/route4me.svg" width="400" height="200" title="Visit our sponsor Route4Me" link="https://route4me.com/">}} {{< sponsor src="/img/logos/route4me.svg" width="400" height="200" title="Visit our sponsor Route4Me" link="https://route4me.com/">}}
{{< sponsor src="/img/logos/rcloneview-banner.svg" width="300" height="200" title="Visit our sponsor RcloneView" link="https://rcloneview.com/">}} {{< sponsor src="/img/logos/rcloneview.svg" width="300" height="200" title="Visit our sponsor RcloneView" link="https://rcloneview.com/">}}
{{< sponsor src="/img/logos/rcloneui.svg" width="300" height="200" title="Visit our sponsor RcloneUI" link="https://github.com/rclone-ui/rclone-ui">}} {{< sponsor src="/img/logos/rcloneui.svg" width="300" height="200" title="Visit our sponsor RcloneUI" link="https://github.com/rclone-ui/rclone-ui">}}
{{< sponsor src="/img/logos/shade.svg" width="300" height="200" title="Visit our sponsor Shade" link="https://shade.inc">}} {{< sponsor src="/img/logos/shade.svg" width="300" height="200" title="Visit our sponsor Shade" link="https://shade.inc">}}
{{< sponsor src="/img/logos/filelu-rclone.svg" width="300" height="200" title="Visit our sponsor FileLu" link="https://filelu.com/">}} {{< sponsor src="/img/logos/filelu-rclone.svg" width="300" height="200" title="Visit our sponsor FileLu" link="https://filelu.com/">}}
{{< sponsor src="/img/logos/torbox.png" width="200" height="200" title="Visit our sponsor TORBOX" link="https://www.torbox.app/">}} {{< sponsor src="/img/logos/torbox.png" width="200" height="200" title="Visit our sponsor TORBOX" link="https://www.torbox.app/">}}
{{< sponsor src="/img/logos/spectra-logic.svg" width="300" height="200" title="Visit our sponsor Spectra Logic" link="https://spectralogic.com/">}} {{< sponsor src="/img/logos/spectra-logic.svg" width="300" height="200" title="Visit our sponsor Spectra Logic" link="https://spectralogic.com/">}}
{{< sponsor src="/img/logos/servercore.svg" width="300" height="200" title="Visit our sponsor servercore" link="https://servercore.com/services/object-storage/?utm_source=rclone.org&utm_medium=referral&utm_campaign=cloud-s3_rclone_231025_paid">}} {{< sponsor src="/img/logos/servercore.svg" width="300" height="200" title="Visit our sponsor servercore" link="https://servercore.com/services/object-storage/?utm_source=rclone.org&utm_medium=referral&utm_campaign=cloud-s3_rclone_231025_paid">}}
{{< sponsor src="/img/logos/exchangerate-api.png" width="300" height="200" title="Visit our sponsor ExchangeRate-API" link="https://www.exchangerate-api.com/">}}
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

View File

@@ -12,6 +12,7 @@ security: High
virtual: false virtual: false
remote: 'TestDrime:' remote: 'TestDrime:'
features: features:
- About
- CanHaveEmptyDirectories - CanHaveEmptyDirectories
- Copy - Copy
- DirCacheFlush - DirCacheFlush

View File

@@ -12,7 +12,7 @@
<div class="card"> <div class="card">
<div class="card-header">Platinum Sponsor</div> <div class="card-header">Platinum Sponsor</div>
<div class="card-body"> <div class="card-body">
<a href="https://rabata.io/?utm_source=banner&utm_medium=rclone&utm_content=general" target="_blank" rel="noopener" title="Visit rclone's sponsor Rabata.io"><img src="/img/logos/rabata.svg"></a><br /> <a href="https://internxt.com/specialoffer/rclone" target="_blank" rel="noopener" title="Visit rclone's sponsor Internxt"><img style="max-width: 100%; height: auto;" src="/img/logos/internxt.jpg"></a><br />
</div> </div>
</div> </div>
@@ -30,13 +30,6 @@
</div> </div>
</div> </div>
<div class="card">
<div class="card-header">Gold Sponsor</div>
<div class="card-body">
<a href="https://internxt.com/?utm_source=rclone" target="_blank" rel="noopener" title="Visit rclone's sponsor Internxt"><img style="max-width: 100%; height: auto;" src="/img/logos/internxt.jpg"></a><br />
</div>
</div>
{{if .IsHome}} {{if .IsHome}}
<div class="card"> <div class="card">
<div class="card-header">Silver Sponsor</div> <div class="card-header">Silver Sponsor</div>
@@ -44,12 +37,6 @@
<a href="https://rcloneview.com/?utm_source=rclone" target="_blank" rel="noopener" title="Visit rclone's sponsor RcloneView"><img src="/img/logos/rcloneview.svg"></a><br /> <a href="https://rcloneview.com/?utm_source=rclone" target="_blank" rel="noopener" title="Visit rclone's sponsor RcloneView"><img src="/img/logos/rcloneview.svg"></a><br />
</div> </div>
</div> </div>
<div class="card">
<div class="card-header">Silver Sponsor</div>
<div class="card-body">
<a href="https://rcloneui.com" target="_blank" rel="noopener" title="Visit rclone's sponsor rclone UI"><img src="/img/logos/rcloneui.svg"></a><br />
</div>
</div>
<div class="card"> <div class="card">
<div class="card-header">Silver Sponsor</div> <div class="card-header">Silver Sponsor</div>
<div class="card-body"> <div class="card-body">

View File

@@ -1 +1 @@
v1.73.0 v1.74.0

View File

@@ -575,8 +575,11 @@ func (acc *Account) String() string {
} }
} }
var displaySpeedString string
if acc.ci.DataRateUnit == "bits" { if acc.ci.DataRateUnit == "bits" {
cur *= 8 displaySpeedString = fs.SizeSuffix(cur * 8).BitRateUnit()
} else {
displaySpeedString = fs.SizeSuffix(cur).ByteRateUnit()
} }
percentageDone := 0 percentageDone := 0
@@ -584,12 +587,12 @@ func (acc *Account) String() string {
percentageDone = int(100 * float64(a) / float64(b)) percentageDone = int(100 * float64(a) / float64(b))
} }
return fmt.Sprintf("%*s:%3d%% /%s, %s/s, %s", return fmt.Sprintf("%*s:%3d%% / %s, %s, %s",
acc.ci.StatsFileNameLength, acc.ci.StatsFileNameLength,
shortenName(acc.name, acc.ci.StatsFileNameLength), shortenName(acc.name, acc.ci.StatsFileNameLength),
percentageDone, percentageDone,
fs.SizeSuffix(b), fs.SizeSuffix(b).ByteUnit(),
fs.SizeSuffix(cur), displaySpeedString,
etas, etas,
) )
} }

View File

@@ -183,14 +183,14 @@ func TestAccountString(t *testing.T) {
// FIXME not an exhaustive test! // FIXME not an exhaustive test!
assert.Equal(t, "test: 0% /3, 0/s, -", strings.TrimSpace(acc.String())) assert.Equal(t, "test: 0% / 3 B, 0 B/s, -", strings.TrimSpace(acc.String()))
var buf = make([]byte, 2) var buf = make([]byte, 2)
n, err := acc.Read(buf) n, err := acc.Read(buf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 2, n) assert.Equal(t, 2, n)
assert.Equal(t, "test: 66% /3, 0/s, -", strings.TrimSpace(acc.String())) assert.Equal(t, "test: 66% / 3 B, 0 B/s, -", strings.TrimSpace(acc.String()))
assert.NoError(t, acc.Close()) assert.NoError(t, acc.Close())
} }

View File

@@ -385,12 +385,14 @@ func (sg *statsGroups) sum(ctx context.Context) *StatsInfo {
sum.checkQueueSize += stats.checkQueueSize sum.checkQueueSize += stats.checkQueueSize
sum.transfers += stats.transfers sum.transfers += stats.transfers
sum.transferring.merge(stats.transferring) sum.transferring.merge(stats.transferring)
sum.transferQueue += stats.transferQueue
sum.transferQueueSize += stats.transferQueueSize sum.transferQueueSize += stats.transferQueueSize
sum.listed += stats.listed sum.listed += stats.listed
sum.renames += stats.renames sum.renames += stats.renames
sum.renameQueue += stats.renameQueue sum.renameQueue += stats.renameQueue
sum.renameQueueSize += stats.renameQueueSize sum.renameQueueSize += stats.renameQueueSize
sum.deletes += stats.deletes sum.deletes += stats.deletes
sum.deletesSize += stats.deletesSize
sum.deletedDirs += stats.deletedDirs sum.deletedDirs += stats.deletedDirs
sum.inProgress.merge(stats.inProgress) sum.inProgress.merge(stats.inProgress)
sum.startedTransfers = append(sum.startedTransfers, stats.startedTransfers...) sum.startedTransfers = append(sum.startedTransfers, stats.startedTransfers...)
@@ -399,6 +401,10 @@ func (sg *statsGroups) sum(ctx context.Context) *StatsInfo {
stats.average.mu.Lock() stats.average.mu.Lock()
sum.average.speed += stats.average.speed sum.average.speed += stats.average.speed
stats.average.mu.Unlock() stats.average.mu.Unlock()
sum.serverSideCopies += stats.serverSideCopies
sum.serverSideCopyBytes += stats.serverSideCopyBytes
sum.serverSideMoves += stats.serverSideMoves
sum.serverSideMoveBytes += stats.serverSideMoveBytes
} }
stats.mu.RUnlock() stats.mu.RUnlock()
} }

View File

@@ -19,7 +19,7 @@ type TransferSnapshot struct {
Checked bool `json:"checked"` Checked bool `json:"checked"`
What string `json:"what"` What string `json:"what"`
StartedAt time.Time `json:"started_at"` StartedAt time.Time `json:"started_at"`
CompletedAt time.Time `json:"completed_at,omitempty"` CompletedAt time.Time `json:"completed_at"`
Error error `json:"-"` Error error `json:"-"`
Group string `json:"group"` Group string `json:"group"`
SrcFs string `json:"srcFs,omitempty"` SrcFs string `json:"srcFs,omitempty"`

View File

@@ -265,9 +265,7 @@ func testAsyncReaderClose(t *testing.T, writeto bool) {
var copyErr error var copyErr error
var wg sync.WaitGroup var wg sync.WaitGroup
started := make(chan struct{}) started := make(chan struct{})
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
close(started) close(started)
if writeto { if writeto {
// exercise the WriteTo path // exercise the WriteTo path
@@ -284,7 +282,7 @@ func testAsyncReaderClose(t *testing.T, writeto bool) {
} }
} }
} }
}() })
// Do some copying // Do some copying
<-started <-started
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)

View File

@@ -163,7 +163,7 @@ type Item struct {
// struct, otherwise they will be embedded as they are. // struct, otherwise they will be embedded as they are.
func Items(opt any) (items []Item, err error) { func Items(opt any) (items []Item, err error) {
def := reflect.ValueOf(opt) def := reflect.ValueOf(opt)
if def.Kind() != reflect.Ptr { if def.Kind() != reflect.Pointer {
return nil, errors.New("argument must be a pointer") return nil, errors.New("argument must be a pointer")
} }
def = def.Elem() // indirect the pointer def = def.Elem() // indirect the pointer

View File

@@ -156,7 +156,7 @@ func Trace(o any, format string, a ...any) func(string, ...any) {
for i := range a { for i := range a {
// read the values of the pointed to items // read the values of the pointed to items
typ := reflect.TypeOf(a[i]) typ := reflect.TypeOf(a[i])
if typ.Kind() == reflect.Ptr { if typ.Kind() == reflect.Pointer {
value := reflect.ValueOf(a[i]) value := reflect.ValueOf(a[i])
if value.IsNil() { if value.IsNil() {
a[i] = nil a[i] = nil

View File

@@ -16,6 +16,7 @@ import (
"github.com/rclone/rclone/fs/list" "github.com/rclone/rclone/fs/list"
"github.com/rclone/rclone/fs/walk" "github.com/rclone/rclone/fs/walk"
"github.com/rclone/rclone/lib/transform" "github.com/rclone/rclone/lib/transform"
"golang.org/x/sync/semaphore"
"golang.org/x/text/unicode/norm" "golang.org/x/text/unicode/norm"
) )
@@ -41,9 +42,10 @@ type March struct {
NoCheckDest bool // transfer all objects regardless without checking dst NoCheckDest bool // transfer all objects regardless without checking dst
NoUnicodeNormalization bool // don't normalize unicode characters in filenames NoUnicodeNormalization bool // don't normalize unicode characters in filenames
// internal state // internal state
srcListDir listDirFn // function to call to list a directory in the src srcListDir listDirFn // function to call to list a directory in the src
dstListDir listDirFn // function to call to list a directory in the dst dstListDir listDirFn // function to call to list a directory in the dst
transforms []matchTransformFn transforms []matchTransformFn
newObjectSem *semaphore.Weighted // make sure we don't call too many NewObjects simultaneously
} }
// Marcher is called on each match // Marcher is called on each match
@@ -78,6 +80,8 @@ func (m *March) init(ctx context.Context) {
if m.Fdst.Features().CaseInsensitive || ci.IgnoreCaseSync { if m.Fdst.Features().CaseInsensitive || ci.IgnoreCaseSync {
m.transforms = append(m.transforms, strings.ToLower) m.transforms = append(m.transforms, strings.ToLower)
} }
// Only allow ci.Checkers simultaneous calls to NewObject
m.newObjectSem = semaphore.NewWeighted(int64(ci.Checkers))
} }
// srcOrDstKey turns a directory entry into a sort key using the defined transforms. // srcOrDstKey turns a directory entry into a sort key using the defined transforms.
@@ -201,9 +205,7 @@ func (m *March) Run(ctx context.Context) error {
checkers := ci.Checkers checkers := ci.Checkers
in := make(chan listDirJob, checkers) in := make(chan listDirJob, checkers)
for range checkers { for range checkers {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
for { for {
select { select {
case <-m.Ctx.Done(): case <-m.Ctx.Done():
@@ -240,7 +242,7 @@ func (m *March) Run(ctx context.Context) error {
traversing.Done() traversing.Done()
} }
} }
}() })
} }
// Start the process // Start the process
@@ -389,9 +391,7 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
// List the src and dst directories // List the src and dst directories
if !job.noSrc { if !job.noSrc {
srcChan := srcChan // duplicate this as we may override it later srcChan := srcChan // duplicate this as we may override it later
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
srcListErr = m.srcListDir(job.srcRemote, func(entries fs.DirEntries) error { srcListErr = m.srcListDir(job.srcRemote, func(entries fs.DirEntries) error {
for _, entry := range entries { for _, entry := range entries {
srcChan <- entry srcChan <- entry
@@ -399,16 +399,14 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
return nil return nil
}) })
close(srcChan) close(srcChan)
}() })
} else { } else {
close(srcChan) close(srcChan)
} }
startedDst := false startedDst := false
if !m.NoTraverse && !job.noDst { if !m.NoTraverse && !job.noDst {
startedDst = true startedDst = true
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
dstListErr = m.dstListDir(job.dstRemote, func(entries fs.DirEntries) error { dstListErr = m.dstListDir(job.dstRemote, func(entries fs.DirEntries) error {
for _, entry := range entries { for _, entry := range entries {
dstChan <- entry dstChan <- entry
@@ -416,7 +414,7 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
return nil return nil
}) })
close(dstChan) close(dstChan)
}() })
} }
// If NoTraverse is set, then try to find a matching object // If NoTraverse is set, then try to find a matching object
// for each item in the srcList to head dst object // for each item in the srcList to head dst object
@@ -451,9 +449,7 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
// Get the tasks from the queue and find a matching object. // Get the tasks from the queue and find a matching object.
var workerWg sync.WaitGroup var workerWg sync.WaitGroup
for range workers { for range workers {
workerWg.Add(1) workerWg.Go(func() {
go func() {
defer workerWg.Done()
for t := range matchTasks { for t := range matchTasks {
// Can't match directories with NewObject // Can't match directories with NewObject
if _, ok := t.src.(fs.Object); !ok { if _, ok := t.src.(fs.Object); !ok {
@@ -461,13 +457,18 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
continue continue
} }
leaf := path.Base(t.src.Remote()) leaf := path.Base(t.src.Remote())
if err := m.newObjectSem.Acquire(m.Ctx, 1); err != nil {
t.dstMatch <- nil
continue
}
dst, err := m.Fdst.NewObject(m.Ctx, path.Join(job.dstRemote, leaf)) dst, err := m.Fdst.NewObject(m.Ctx, path.Join(job.dstRemote, leaf))
m.newObjectSem.Release(1)
if err != nil { if err != nil {
dst = nil dst = nil
} }
t.dstMatch <- dst t.dstMatch <- dst
} }
}() })
} }
// Close dstResults when all the workers have finished // Close dstResults when all the workers have finished
@@ -477,9 +478,7 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
}() }()
// Read the matches in order and send them to dstChan if found. // Read the matches in order and send them to dstChan if found.
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
for dstMatch := range dstMatches { for dstMatch := range dstMatches {
dst := <-dstMatch dst := <-dstMatch
// Note that dst may be nil here // Note that dst may be nil here
@@ -488,7 +487,7 @@ func (m *March) processJob(job listDirJob) ([]listDirJob, error) {
} }
close(srcChan) close(srcChan)
close(dstChan) close(dstChan)
}() })
} }
if !startedDst { if !startedDst {
close(dstChan) close(dstChan)

View File

@@ -508,9 +508,7 @@ func TestMatchListings(t *testing.T) {
} }
ls, err := list.NewSorter(ctx, nil, list.SortToChan(out), key) ls, err := list.NewSorter(ctx, nil, list.SortToChan(out), key)
require.NoError(t, err) require.NoError(t, err)
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
for i := 0; i < len(test.input); i += 2 { for i := 0; i < len(test.input); i += 2 {
entry := test.input[i+offset] entry := test.input[i+offset]
if entry != nil { if entry != nil {
@@ -520,7 +518,7 @@ func TestMatchListings(t *testing.T) {
require.NoError(t, ls.Send()) require.NoError(t, ls.Send())
ls.CleanUp() ls.CleanUp()
close(out) close(out)
}() })
return out return out
} }

View File

@@ -570,13 +570,11 @@ func (s *syncCopyMove) startTrackRenames() {
if !s.trackRenames { if !s.trackRenames {
return return
} }
s.trackRenamesWg.Add(1) s.trackRenamesWg.Go(func() {
go func() {
defer s.trackRenamesWg.Done()
for o := range s.trackRenamesCh { for o := range s.trackRenamesCh {
s.renameCheck = append(s.renameCheck, o) s.renameCheck = append(s.renameCheck, o)
} }
}() })
} }
// This stops the background rename collection // This stops the background rename collection
@@ -593,12 +591,10 @@ func (s *syncCopyMove) startDeleters() {
if s.deleteMode != fs.DeleteModeDuring && s.deleteMode != fs.DeleteModeOnly { if s.deleteMode != fs.DeleteModeDuring && s.deleteMode != fs.DeleteModeOnly {
return return
} }
s.deletersWg.Add(1) s.deletersWg.Go(func() {
go func() {
defer s.deletersWg.Done()
err := operations.DeleteFilesWithBackupDir(s.ctx, s.deleteFilesCh, s.backupDir) err := operations.DeleteFilesWithBackupDir(s.ctx, s.deleteFilesCh, s.backupDir)
s.processError(err) s.processError(err)
}() })
} }
// This stops the background deleters // This stops the background deleters

View File

@@ -1,7 +1,7 @@
//go:build !go1.24 //go:build !go1.25
package fs package fs
// Upgrade to Go version 1.24 to compile rclone - latest stable go // Upgrade to Go version 1.25 to compile rclone - latest stable go
// compiler recommended. // compiler recommended.
func init() { Go_version_1_24_required_for_compilation() } func init() { Go_version_1_25_required_for_compilation() }

View File

@@ -1,4 +1,4 @@
package fs package fs
// VersionTag of rclone // VersionTag of rclone
var VersionTag = "v1.73.0" var VersionTag = "v1.74.0"

View File

@@ -391,9 +391,7 @@ func walk(ctx context.Context, f fs.Fs, path string, includeAll bool, maxLevel i
}) })
} }
for range ci.Checkers { for range ci.Checkers {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
for { for {
select { select {
case job, ok := <-in: case job, ok := <-in:
@@ -442,7 +440,7 @@ func walk(ctx context.Context, f fs.Fs, path string, includeAll bool, maxLevel i
return return
} }
} }
}() })
} }
// Start the process // Start the process
traversing.Add(1) traversing.Add(1)

View File

@@ -678,6 +678,7 @@ backends:
- backend: "internxt" - backend: "internxt"
remote: "TestInternxt:" remote: "TestInternxt:"
fastlist: false fastlist: false
listretries: 5
ignore: ignore:
- TestRWFileHandleWriteNoWrite - TestRWFileHandleWriteNoWrite
- backend: "drime" - backend: "drime"

179
go.mod
View File

@@ -1,35 +1,35 @@
module github.com/rclone/rclone module github.com/rclone/rclone
go 1.24.4 go 1.25.0
godebug x509negativeserial=1 godebug x509negativeserial=1
require ( require (
bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5 bazil.org/fuse v0.0.0-20230120002735-62a210ff1fd5
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.3 github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.4
github.com/Azure/go-ntlmssp v0.0.2-0.20251110135918-10b7b7e7cd26 github.com/Azure/go-ntlmssp v0.1.0
github.com/FilenCloudDienste/filen-sdk-go v0.0.35 github.com/FilenCloudDienste/filen-sdk-go v0.0.37
github.com/Files-com/files-sdk-go/v3 v3.2.264 github.com/Files-com/files-sdk-go/v3 v3.3.40
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd
github.com/a1ex3/zstd-seekable-format-go/pkg v0.10.0 github.com/a1ex3/zstd-seekable-format-go/pkg v0.10.0
github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e
github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3 github.com/aalpar/deheap v1.0.0
github.com/abbot/go-http-auth v0.4.0 github.com/abbot/go-http-auth v0.4.0
github.com/anacrolix/dms v1.7.2 github.com/anacrolix/dms v1.7.2
github.com/anacrolix/log v0.17.0 github.com/anacrolix/log v0.17.0
github.com/atotto/clipboard v0.1.4 github.com/atotto/clipboard v0.1.4
github.com/aws/aws-sdk-go-v2 v1.39.6 github.com/aws/aws-sdk-go-v2 v1.41.1
github.com/aws/aws-sdk-go-v2/config v1.31.17 github.com/aws/aws-sdk-go-v2/config v1.32.8
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 github.com/aws/aws-sdk-go-v2/credentials v1.19.8
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.4 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 github.com/aws/aws-sdk-go-v2/service/sts v1.41.6
github.com/aws/smithy-go v1.23.2 github.com/aws/smithy-go v1.24.0
github.com/buengese/sgzip v0.1.1 github.com/buengese/sgzip v0.1.1
github.com/cloudinary/cloudinary-go/v2 v2.13.0 github.com/cloudinary/cloudinary-go/v2 v2.14.1
github.com/cloudsoda/go-smb2 v0.0.0-20250228001242-d4c70e6251cc github.com/cloudsoda/go-smb2 v0.0.0-20250228001242-d4c70e6251cc
github.com/colinmarc/hdfs/v2 v2.4.0 github.com/colinmarc/hdfs/v2 v2.4.0
github.com/coreos/go-semver v0.3.1 github.com/coreos/go-semver v0.3.1
@@ -37,31 +37,31 @@ require (
github.com/diskfs/go-diskfs v1.7.0 github.com/diskfs/go-diskfs v1.7.0
github.com/dop251/scsu v0.0.0-20220106150536-84ac88021d00 github.com/dop251/scsu v0.0.0-20220106150536-84ac88021d00
github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5 github.com/dropbox/dropbox-sdk-go-unofficial/v6 v6.0.5
github.com/gabriel-vasile/mimetype v1.4.11 github.com/gabriel-vasile/mimetype v1.4.13
github.com/gdamore/tcell/v2 v2.9.0 github.com/gdamore/tcell/v2 v2.13.8
github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/chi/v5 v5.2.5
github.com/go-darwin/apfs v0.0.0-20211011131704-f84b94dbf348 github.com/go-darwin/apfs v0.0.0-20211011131704-f84b94dbf348
github.com/go-git/go-billy/v5 v5.6.2 github.com/go-git/go-billy/v5 v5.7.0
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/hanwen/go-fuse/v2 v2.9.0 github.com/hanwen/go-fuse/v2 v2.9.0
github.com/internxt/rclone-adapter v0.0.0-20260130171252-c3c6ebb49276 github.com/internxt/rclone-adapter v0.0.0-20260213125353-6f59c89fcb7c
github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3 github.com/jlaffaye/ftp v0.2.1-0.20240918233326-1b970516f5d3
github.com/josephspurrier/goversioninfo v1.5.0 github.com/josephspurrier/goversioninfo v1.5.0
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004
github.com/klauspost/compress v1.18.1 github.com/klauspost/compress v1.18.4
github.com/koofr/go-httpclient v0.0.0-20240520111329-e20f8f203988 github.com/koofr/go-httpclient v0.0.0-20240520111329-e20f8f203988
github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6 github.com/koofr/go-koofrclient v0.0.0-20221207135200-cbd7fc9ad6a6
github.com/lanrat/extsort v1.4.2 github.com/lanrat/extsort v1.4.2
github.com/mattn/go-colorable v0.1.14 github.com/mattn/go-colorable v0.1.14
github.com/mattn/go-runewidth v0.0.19 github.com/mattn/go-runewidth v0.0.20
github.com/mholt/archives v0.1.5 github.com/mholt/archives v0.1.5
github.com/minio/minio-go/v7 v7.0.97 github.com/minio/minio-go/v7 v7.0.98
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/moby/sys/mountinfo v0.7.2 github.com/moby/sys/mountinfo v0.7.2
github.com/ncw/swift/v2 v2.0.5 github.com/ncw/swift/v2 v2.0.5
github.com/oracle/oci-go-sdk/v65 v65.104.0 github.com/oracle/oci-go-sdk/v65 v65.108.2
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/peterh/liner v1.2.2 github.com/peterh/liner v1.2.2
github.com/pkg/sftp v1.13.10 github.com/pkg/sftp v1.13.10
@@ -72,15 +72,15 @@ require (
github.com/rclone/Proton-API-Bridge v1.0.1-0.20260127174007-77f974840d11 github.com/rclone/Proton-API-Bridge v1.0.1-0.20260127174007-77f974840d11
github.com/rclone/go-proton-api v1.0.1-0.20260127173028-eb465cac3b18 github.com/rclone/go-proton-api v1.0.1-0.20260127173028-eb465cac3b18
github.com/rclone/gofakes3 v0.0.4 github.com/rclone/gofakes3 v0.0.4
github.com/rfjakob/eme v1.1.2 github.com/rfjakob/eme v1.2.0
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
github.com/rogpeppe/go-internal v1.14.1 github.com/rogpeppe/go-internal v1.14.1
github.com/shirou/gopsutil/v4 v4.25.10 github.com/shirou/gopsutil/v4 v4.26.1
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/t3rm1n4l/go-mega v0.0.0-20251031123324-a804aaa87491 github.com/t3rm1n4l/go-mega v0.0.0-20251120131202-6845944c051c
github.com/unknwon/goconfig v1.0.0 github.com/unknwon/goconfig v1.0.0
github.com/willscott/go-nfs v0.0.3 github.com/willscott/go-nfs v0.0.3
github.com/winfsp/cgofuse v1.6.1-0.20260126094232-f2c4fccdb286 github.com/winfsp/cgofuse v1.6.1-0.20260126094232-f2c4fccdb286
@@ -89,17 +89,17 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78
github.com/yunify/qingstor-sdk-go/v3 v3.2.0 github.com/yunify/qingstor-sdk-go/v3 v3.2.0
github.com/zeebo/blake3 v0.2.4 github.com/zeebo/blake3 v0.2.4
github.com/zeebo/xxh3 v1.0.2 github.com/zeebo/xxh3 v1.1.0
go.etcd.io/bbolt v1.4.3 go.etcd.io/bbolt v1.4.3
goftp.io/server/v2 v2.0.2 goftp.io/server/v2 v2.0.2
golang.org/x/crypto v0.45.0 golang.org/x/crypto v0.48.0
golang.org/x/net v0.47.0 golang.org/x/net v0.50.0
golang.org/x/oauth2 v0.33.0 golang.org/x/oauth2 v0.35.0
golang.org/x/sync v0.18.0 golang.org/x/sync v0.19.0
golang.org/x/sys v0.38.0 golang.org/x/sys v0.41.0
golang.org/x/text v0.31.0 golang.org/x/text v0.34.0
golang.org/x/time v0.14.0 golang.org/x/time v0.14.0
google.golang.org/api v0.255.0 google.golang.org/api v0.267.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/validator.v2 v2.0.1 gopkg.in/validator.v2 v2.0.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
@@ -107,7 +107,7 @@ require (
) )
require ( require (
cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth v0.18.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
@@ -117,26 +117,27 @@ require (
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/ProtonMail/go-srp v0.0.7 // indirect github.com/ProtonMail/go-srp v0.0.7 // indirect
github.com/ProtonMail/gopenpgp/v2 v2.9.0 // indirect github.com/ProtonMail/gopenpgp/v2 v2.9.0 // indirect
github.com/PuerkitoBio/goquery v1.10.3 // indirect github.com/PuerkitoBio/goquery v1.11.0 // indirect
github.com/STARRY-S/zip v0.2.3 // indirect github.com/STARRY-S/zip v0.2.3 // indirect
github.com/akavel/rsrc v0.10.2 // indirect github.com/akavel/rsrc v0.10.2 // indirect
github.com/anacrolix/generics v0.1.0 // indirect github.com/anacrolix/generics v0.2.0 // indirect
github.com/anchore/go-lzo v0.1.0 // indirect github.com/anchore/go-lzo v0.1.0 // indirect
github.com/andybalholm/brotli v1.2.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc // indirect github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/plumbing v1.3.0 // indirect
@@ -149,9 +150,8 @@ require (
github.com/calebcase/tmpfile v1.0.3 // indirect github.com/calebcase/tmpfile v1.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9 // indirect github.com/chilts/sid v0.0.0-20190607042430-660e94789ec9 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc // indirect github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/creasty/defaults v1.8.0 // indirect github.com/creasty/defaults v1.8.0 // indirect
@@ -171,19 +171,19 @@ require (
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-openapi/errors v0.22.4 // indirect github.com/go-openapi/errors v0.22.6 // indirect
github.com/go-openapi/strfmt v0.25.0 // indirect github.com/go-openapi/strfmt v0.25.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.28.0 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/go-resty/resty/v2 v2.16.5 // indirect github.com/go-resty/resty/v2 v2.17.2 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/gofrs/flock v0.13.0 // indirect github.com/gofrs/flock v0.13.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/s2a-go v0.1.9 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect
github.com/gorilla/schema v1.4.1 // indirect github.com/gorilla/schema v1.4.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -206,7 +206,7 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lpar/date v1.0.0 // indirect github.com/lpar/date v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
github.com/mailru/easyjson v0.9.1 // indirect github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mikelolasagasti/xz v1.0.1 // indirect github.com/mikelolasagasti/xz v1.0.1 // indirect
@@ -215,17 +215,17 @@ require (
github.com/minio/minlz v1.0.1 // indirect github.com/minio/minlz v1.0.1 // indirect
github.com/minio/xxml v0.0.3 // indirect github.com/minio/xxml v0.0.3 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode/v2 v2.2.1 // indirect github.com/nwaples/rardecode/v2 v2.2.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/panjf2000/ants/v2 v2.11.3 // indirect github.com/panjf2000/ants/v2 v2.11.5 // indirect
github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 // indirect github.com/pengsrc/go-shared v0.2.1-0.20190131101655-1999055a4a14 // indirect
github.com/philhofer/fwd v1.2.0 // indirect github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect github.com/pierrec/lz4/v4 v4.1.25 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.2 // indirect github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.19.2 // indirect github.com/prometheus/procfs v0.19.2 // indirect
github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect
github.com/relvacode/iso8601 v1.7.0 // indirect github.com/relvacode/iso8601 v1.7.0 // indirect
@@ -235,38 +235,39 @@ require (
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect
github.com/samber/lo v1.52.0 // indirect github.com/samber/lo v1.52.0 // indirect
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df // indirect github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df // indirect
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af // indirect github.com/sirupsen/logrus v1.9.4 // indirect
github.com/smartystreets/goconvey v1.8.1 // indirect github.com/smartystreets/goconvey v1.8.1 // indirect
github.com/sony/gobreaker v1.0.0 // indirect github.com/sony/gobreaker v1.0.0 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/sorairolake/lzip-go v0.3.8 // indirect
github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368 // indirect github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368 // indirect
github.com/spf13/afero v1.15.0 // indirect github.com/spf13/afero v1.15.0 // indirect
github.com/tinylib/msgp v1.5.0 // indirect github.com/tinylib/msgp v1.6.3 // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect github.com/tklauser/numcpus v0.11.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect github.com/ulikunitz/xz v0.5.15 // indirect
github.com/willscott/go-nfs-client v0.0.0-20251022144359-801f10d98886 // indirect github.com/willscott/go-nfs-client v0.0.0-20251022144359-801f10d98886 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zeebo/errs v1.4.0 // indirect github.com/zeebo/errs v1.4.0 // indirect
go.mongodb.org/mongo-driver v1.17.6 // indirect go.mongodb.org/mongo-driver v1.17.9 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect
golang.org/x/image v0.32.0 // indirect golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
golang.org/x/mod v0.29.0 // indirect golang.org/x/image v0.36.0 // indirect
golang.org/x/tools v0.38.0 // indirect golang.org/x/tools v0.42.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d // indirect
google.golang.org/grpc v1.76.0 // indirect google.golang.org/grpc v1.79.1 // indirect
google.golang.org/protobuf v1.36.10 // indirect google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
moul.io/http2curl/v2 v2.3.0 // indirect moul.io/http2curl/v2 v2.3.0 // indirect
storj.io/common v0.0.0-20251107171817-6221ae45072c // indirect sigs.k8s.io/yaml v1.6.0 // indirect
storj.io/common v0.0.0-20260212175235-9580cc9c5777 // indirect
storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55 // indirect storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55 // indirect
storj.io/eventkit v0.0.0-20250410172343-61f26d3de156 // indirect storj.io/eventkit v0.0.0-20250410172343-61f26d3de156 // indirect
storj.io/infectious v0.0.2 // indirect storj.io/infectious v0.0.2 // indirect
@@ -274,12 +275,12 @@ require (
) )
require ( require (
github.com/IBM/go-sdk-core/v5 v5.18.5 github.com/IBM/go-sdk-core/v5 v5.21.2
github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 github.com/ProtonMail/go-crypto v1.3.0
github.com/golang-jwt/jwt/v4 v4.5.2 github.com/golang-jwt/jwt/v4 v4.5.2
github.com/pkg/xattr v0.4.12 github.com/pkg/xattr v0.4.12
github.com/pquerna/otp v1.5.0 github.com/pquerna/otp v1.5.0
golang.org/x/mobile v0.0.0-20251021151156-188f512ec823 golang.org/x/mobile v0.0.0-20260217195705-b56b3793a9c4
golang.org/x/term v0.37.0 golang.org/x/term v0.40.0
) )

377
go.sum
View File

@@ -15,8 +15,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
@@ -39,41 +39,41 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 h1:ZJJNFaQ86GVKQ9ehwqyAFE6pIfyicpuJ8IkVaPBc6/4= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3/go.mod h1:URuDvhmATVKqHBH9/0nOiNKk0+YcwfQ3WkK5PqHKxc8= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.3 h1:sxgSqOB9CDToiaVFpxuvb5wGgGqWa3lCShcm5o0n3bE= github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.4 h1:tZh20RjgfMxKBxJiIS75iTVAKIUxrST5X2dVHMTptL4=
github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.3/go.mod h1:XdED8i399lEVblYHTZM8eXaP07gv4Z58IL6ueMlVlrg= github.com/Azure/azure-sdk-for-go/sdk/storage/azfile v1.5.4/go.mod h1:vGYAk36rhMVCfTP7v+RVruCR0zmPe6S+36KRpDCLySw=
github.com/Azure/go-ntlmssp v0.0.2-0.20251110135918-10b7b7e7cd26 h1:gy/jrlpp8EfSyA73a51fofoSfhp5rPNQAUvDr4Dm91c= github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
github.com/Azure/go-ntlmssp v0.0.2-0.20251110135918-10b7b7e7cd26/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/FilenCloudDienste/filen-sdk-go v0.0.35 h1:geuYpD/1ZXSp1H3kdW7si+KRUIrHHqM1kk8lqoA8Y9M= github.com/FilenCloudDienste/filen-sdk-go v0.0.37 h1:W8S9TrAyZ4//3PXsU6+Bi+fe/6uIL986GyS7PVzIDL4=
github.com/FilenCloudDienste/filen-sdk-go v0.0.35/go.mod h1:0cBhKXQg49XbKZZfk5TCDa3sVLP+xMxZTWL+7KY0XR0= github.com/FilenCloudDienste/filen-sdk-go v0.0.37/go.mod h1:0cBhKXQg49XbKZZfk5TCDa3sVLP+xMxZTWL+7KY0XR0=
github.com/Files-com/files-sdk-go/v3 v3.2.264 h1:lMHTplAYI9FtmCo/QOcpRxmPA5REVAct1r2riQmDQKw= github.com/Files-com/files-sdk-go/v3 v3.3.40 h1:N4zEhSZWrqUNW4m1ta9FFjvmpNjSwyuUnX6HZAzjHFw=
github.com/Files-com/files-sdk-go/v3 v3.2.264/go.mod h1:wGqkOzRu/ClJibvDgcfuJNAqI2nLhe8g91tPlDKRCdE= github.com/Files-com/files-sdk-go/v3 v3.3.40/go.mod h1:IPk80dOmc7VFC0DJ85xMTPmre+8xoXX6kGHAkf5jRRw=
github.com/IBM/go-sdk-core/v5 v5.18.5 h1:g0JRl3sYXJczB/yuDlrN6x22LJ6jIxhp0Sa4ARNW60c= github.com/IBM/go-sdk-core/v5 v5.21.2 h1:mJ5QbLPOm4g5qhZiVB6wbSllfpeUExftGoyPek2hk4M=
github.com/IBM/go-sdk-core/v5 v5.18.5/go.mod h1:KonTFRR+8ZSgw5cxBSYo6E4WZoY1+7n1kfHM82VcjFU= github.com/IBM/go-sdk-core/v5 v5.21.2/go.mod h1:ngpMgwkjur1VNUjqn11LPk3o5eCyOCRbcfg/0YAY7Hc=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE= github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc= github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I= github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug= github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
@@ -88,24 +88,24 @@ github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1J
github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk= github.com/ProtonMail/go-srp v0.0.7/go.mod h1:giCp+7qRnMIcCvI6V6U3S1lDDXDQYx2ewJ6F/9wdlJk=
github.com/ProtonMail/gopenpgp/v2 v2.9.0 h1:ruLzBmwe4dR1hdnrsEJ/S7psSBmV15gFttFUPP/+/kE= github.com/ProtonMail/gopenpgp/v2 v2.9.0 h1:ruLzBmwe4dR1hdnrsEJ/S7psSBmV15gFttFUPP/+/kE=
github.com/ProtonMail/gopenpgp/v2 v2.9.0/go.mod h1:IldDyh9Hv1ZCCYatTuuEt1XZJ0OPjxLpTarDfglih7s= github.com/ProtonMail/gopenpgp/v2 v2.9.0/go.mod h1:IldDyh9Hv1ZCCYatTuuEt1XZJ0OPjxLpTarDfglih7s=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
github.com/a1ex3/zstd-seekable-format-go/pkg v0.10.0 h1:iLDOF0rdGTrol/q8OfPIIs5kLD8XvA2q75o6Uq/tgak= github.com/a1ex3/zstd-seekable-format-go/pkg v0.10.0 h1:iLDOF0rdGTrol/q8OfPIIs5kLD8XvA2q75o6Uq/tgak=
github.com/a1ex3/zstd-seekable-format-go/pkg v0.10.0/go.mod h1:DrEWcQJjz7t5iF2duaiyhg4jyoF0kxOD6LtECNGkZ/Q= github.com/a1ex3/zstd-seekable-format-go/pkg v0.10.0/go.mod h1:DrEWcQJjz7t5iF2duaiyhg4jyoF0kxOD6LtECNGkZ/Q=
github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e h1:KMVieI1/Ub++GYfnhyFPoGE3g5TUiG4srE3TMGr5nM4= github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e h1:KMVieI1/Ub++GYfnhyFPoGE3g5TUiG4srE3TMGr5nM4=
github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e/go.mod h1:j5astEcUkZQX8lK+KKlQ3NRQ50f4EE8ZjyZpCz3mrH4= github.com/a8m/tree v0.0.0-20240104212747-2c8764a5f17e/go.mod h1:j5astEcUkZQX8lK+KKlQ3NRQ50f4EE8ZjyZpCz3mrH4=
github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3 h1:hhdWprfSpFbN7lz3W1gM40vOgvSh1WCSMxYD6gGB4Hs= github.com/aalpar/deheap v1.0.0 h1:vVdXUiQ16b858LraxHgCg1UmRmoObS53gFRi3fFY9x0=
github.com/aalpar/deheap v0.0.0-20210914013432-0cc84d79dec3/go.mod h1:XaUnRxSCYgL3kkgX0QHIV0D+znljPIDImxlv2kbGv0Y= github.com/aalpar/deheap v1.0.0/go.mod h1:A+nfkD4JbS05sewV0he/MYgR/90vfqyMoNNROgs+rmA=
github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0=
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM= github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw= github.com/akavel/rsrc v0.10.2 h1:Zxm8V5eI1hW4gGaYsJQUhxpjkENuG91ki8B4zCrvEsw=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/anacrolix/dms v1.7.2 h1:JAAJJIlXp+jT2yEah1EbR1AFpGALHL238uSKFXec2qw= github.com/anacrolix/dms v1.7.2 h1:JAAJJIlXp+jT2yEah1EbR1AFpGALHL238uSKFXec2qw=
github.com/anacrolix/dms v1.7.2/go.mod h1:excFJW5MKBhn5yt5ZMyeE9iFVqnO6tEGQl7YG/2tUoQ= github.com/anacrolix/dms v1.7.2/go.mod h1:excFJW5MKBhn5yt5ZMyeE9iFVqnO6tEGQl7YG/2tUoQ=
github.com/anacrolix/generics v0.1.0 h1:r6OgogjCdml3K5A8ixUG0X9DM4jrQiMfIkZiBOGvIfg= github.com/anacrolix/generics v0.2.0 h1:gPwGOs14irokFN9kUP1i1A0Bn0FPT7/hWWD3hHKSKNw=
github.com/anacrolix/generics v0.1.0/go.mod h1:MN3ve08Z3zSV/rTuX/ouI4lNdlfTxgdafQJiLzyNRB8= github.com/anacrolix/generics v0.2.0/go.mod h1:NGehhfeXJPBujPx0s6cstSj8B+TERsTY32Xckfx5ftc=
github.com/anacrolix/log v0.17.0 h1:cZvEGRPCbIg+WK+qAxWj/ap2Gj8cx1haOCSVxNZQpK4= github.com/anacrolix/log v0.17.0 h1:cZvEGRPCbIg+WK+qAxWj/ap2Gj8cx1haOCSVxNZQpK4=
github.com/anacrolix/log v0.17.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA= github.com/anacrolix/log v0.17.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs= github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs=
@@ -118,44 +118,46 @@ github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc h1:LoL75er
github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc/go.mod h1:w648aMHEgFYS6xb0KVMMtZ2uMeemhiKCuD2vj6gY52A= github.com/appscode/go-querystring v0.0.0-20170504095604-0126cfb3f1dc/go.mod h1:w648aMHEgFYS6xb0KVMMtZ2uMeemhiKCuD2vj6gY52A=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= github.com/aws/aws-sdk-go-v2 v1.41.1 h1:ABlyEARCDLN034NhxlRUSZr4l71mh+T5KAeGh6cerhU=
github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= github.com/aws/aws-sdk-go-v2 v1.41.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3 h1:DHctwEM8P8iTXFxC/QK0MRjwEpWQeM9yzidCRjldUz0= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.3/go.mod h1:xdCzcZEtnSTKVDOmUZs4l/j3pSV6rpo1WXl5ugNsL8Y= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y= github.com/aws/aws-sdk-go-v2/config v1.32.8 h1:iu+64gwDKEoKnyTQskSku72dAwggKI5sV6rNvgSMpMs=
github.com/aws/aws-sdk-go-v2/config v1.31.17/go.mod h1:V8P7ILjp/Uef/aX8TjGk6OHZN6IKPM5YW6S78QnRD5c= github.com/aws/aws-sdk-go-v2/config v1.32.8/go.mod h1:MI2XvA+qDi3i9AJxX1E2fu730syEBzp/jnXrjxuHwgI=
github.com/aws/aws-sdk-go-v2/credentials v1.18.21 h1:56HGpsgnmD+2/KpG0ikvvR8+3v3COCwaF4r+oWwOeNA= github.com/aws/aws-sdk-go-v2/credentials v1.19.8 h1:Jp2JYH1lRT3KhX4mshHPvVYsR5qqRec3hGvEarNYoR0=
github.com/aws/aws-sdk-go-v2/credentials v1.18.21/go.mod h1:3YELwedmQbw7cXNaII2Wywd+YY58AmLPwX4LzARgmmA= github.com/aws/aws-sdk-go-v2/credentials v1.19.8/go.mod h1:fZG9tuvyVfxknv1rKibIz3DobRaFw1Poe8IKtXB3XYY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 h1:I0GyV8wiYrP8XpA70g1HBcQO1JlQxCMTW9npl5UbDHY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17/go.mod h1:tyw7BOl5bBe/oqvoIeECFJjMdzXoa/dfVz3QQ5lgHGA=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.4 h1:2fjfz3/G9BRvIKuNZ655GwzpklC2kEH0cowZQGO7uBg= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.1 h1:IbWiN670htmBioc+Zj32vSpJgQ2+OYSlvTvfQ1nCORQ=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.20.4/go.mod h1:Ymws824lvMypLFPwyyUXM52SXuGgxpu0+DISLfKvB+c= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.1/go.mod h1:tw/B596EUhBWDFGdDGuLC21fVU4A3s4/5Efy8S39W18=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 h1:xOLELNKGp2vsiteLsvLPwxC+mYmO6OZ8PYgiuPJzF8U=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17/go.mod h1:5M5CI3D12dNOtH3/mk6minaRwI2/37ifCURZISxA/IQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 h1:WWLqlh79iO48yLkj1v3ISRNiv+3KdQoZ6JWyfcsyQik=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17/go.mod h1:EhG22vHRrvF8oXSTYStZhJc1aUgKtnJe+aOiFEV90cM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13 h1:eg/WYAa12vqTphzIdWMzqYRVKKnCboVPRlvaybNCqPA= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17 h1:JqcdRG//czea7Ppjb+g/n4o8i/R50aTBHkA7vu0lK+k=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.13/go.mod h1:/FDdxWhz1486obGrKKC1HONd7krpk38LBt+dutLcN9k= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.17/go.mod h1:CO+WeGmIdj/MlPel2KwID9Gt7CNq4M65HUfBW97liM0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4 h1:NvMjwvv8hpGUILarKw7Z4Q0w1H9anXKsesMxtw++MA4= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8 h1:Z5EiPIzXKewUQK0QTMkutjiaPVeVYXX7KIqhXu/0fXs=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.4/go.mod h1:455WPHSwaGj2waRSpQp7TsnpOnBfw8iDfPfbwl7KPJE= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.8/go.mod h1:FsTpJtvC4U1fyDXk7c71XoDv3HlRm8V3NiYLeYLh5YE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 h1:RuNSMoozM8oXlgLG/n6WLaFGoea7/CddrCfIiSA+xdY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17/go.mod h1:F2xxQ9TZz5gDWsclCtPQscGpP0VUOc8RqgFM3vDENmU=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13 h1:zhBJXdhWIFZ1acfDYIhu4+LCzdUS2Vbcum7D01dXlHQ= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17 h1:bGeHBsGZx0Dvu/eJC0Lh9adJa3M1xREcndxLNZlve2U=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.13/go.mod h1:JaaOeCE368qn2Hzi3sEzY6FgAZVCIYcC2nwbro2QCh8= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.17/go.mod h1:dcW24lbU0CzHusTE8LLHhRLI42ejmINN8Lcr22bwh/g=
github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0 h1:ef6gIJR+xv/JQWwpa5FYirzoQctfSJm7tuDe3SZsUf8= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0 h1:oeu8VPlOre74lBA/PMhxa5vewaMIMmILM+RraSyB8KA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.90.0/go.mod h1:+wArOOrcHUevqdto9k1tKOF5++YTe9JEcPSc9Tx2ZSw= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.0/go.mod h1:5jggDlZ2CLQhwJBiZJb4vfk4f0GxWdEDruWKEJ1xOdo=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ1lIMHMa+AHZSzz0= github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 h1:VrhDvQib/i0lxvr3zqlUwLwJP4fpmpyD9wYG1vfSu+Y=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= github.com/aws/aws-sdk-go-v2/service/signin v1.0.5/go.mod h1:k029+U8SY30/3/ras4G/Fnv/b88N4mAfliNn08Dem4M=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0= github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 h1:v6EiMvhEYBoHABfbGB4alOYmCIrcgyPPiBE1wZAEbqk=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= github.com/aws/aws-sdk-go-v2/service/sso v1.30.9/go.mod h1:yifAsgBxgJWn3ggx70A3urX2AN49Y5sJTD1UQFlfqBw=
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 h1:mLlUgHn02ue8whiR4BmxxGJLR2gwU6s6ZzJ5wDamBUs= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 h1:0jbJeuEHlwKJ9PfXtpSFc4MF+WIWORdhN1n30ITZGFM=
github.com/aws/aws-sdk-go-v2/service/sts v1.39.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14/go.mod h1:sTGThjphYE4Ohw8vJiRStAcu3rbjtXRsdNB0TvZ5wwo=
github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 h1:5fFjR/ToSOzB2OQ/XqWpZBmNvmP/pJ1jOWYlFDJTjRQ=
github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= github.com/aws/aws-sdk-go-v2/service/sts v1.41.6/go.mod h1:qgFDZQSD/Kys7nJnVqYlWKnh0SSdMjAi0uSwON4wgYQ=
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -193,15 +195,13 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudinary/cloudinary-go/v2 v2.13.0 h1:ugiQwb7DwpWQnete2AZkTh94MonZKmxD7hDGy1qTzDs= github.com/cloudinary/cloudinary-go/v2 v2.14.1 h1:PK2pjdNl0OMuo5IvbwHF6o8uEzafD66q6LIYFAqt3ic=
github.com/cloudinary/cloudinary-go/v2 v2.13.0/go.mod h1:ireC4gqVetsjVhYlwjUJwKTbZuWjEIynbR9zQTlqsvo= github.com/cloudinary/cloudinary-go/v2 v2.14.1/go.mod h1:ireC4gqVetsjVhYlwjUJwKTbZuWjEIynbR9zQTlqsvo=
github.com/cloudsoda/go-smb2 v0.0.0-20250228001242-d4c70e6251cc h1:t8YjNUCt1DimB4HCIXBztwWMhgxr5yG5/YaRl9Afdfg= github.com/cloudsoda/go-smb2 v0.0.0-20250228001242-d4c70e6251cc h1:t8YjNUCt1DimB4HCIXBztwWMhgxr5yG5/YaRl9Afdfg=
github.com/cloudsoda/go-smb2 v0.0.0-20250228001242-d4c70e6251cc/go.mod h1:CgWpFCFWzzEA5hVkhAc6DZZzGd3czx+BblvOzjmg6KA= github.com/cloudsoda/go-smb2 v0.0.0-20250228001242-d4c70e6251cc/go.mod h1:CgWpFCFWzzEA5hVkhAc6DZZzGd3czx+BblvOzjmg6KA=
github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc h1:0xCWmFKBmarCqqqLeM7jFBSw/Or81UEElFqO8MY+GDs= github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc h1:0xCWmFKBmarCqqqLeM7jFBSw/Or81UEElFqO8MY+GDs=
@@ -268,24 +268,24 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys= github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
github.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo= github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
github.com/geoffgarside/ber v1.2.0 h1:/loowoRcs/MWLYmGX9QtIAbA+V/FrnVLsMMPhwiRm64= github.com/geoffgarside/ber v1.2.0 h1:/loowoRcs/MWLYmGX9QtIAbA+V/FrnVLsMMPhwiRm64=
github.com/geoffgarside/ber v1.2.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/geoffgarside/ber v1.2.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-darwin/apfs v0.0.0-20211011131704-f84b94dbf348 h1:JnrjqG5iR07/8k7NqrLNilRsl3s1EPRQEGvbPyOce68= github.com/go-darwin/apfs v0.0.0-20211011131704-f84b94dbf348 h1:JnrjqG5iR07/8k7NqrLNilRsl3s1EPRQEGvbPyOce68=
github.com/go-darwin/apfs v0.0.0-20211011131704-f84b94dbf348/go.mod h1:Czxo/d1g948LtrALAZdL04TL/HnkopquAjxYUuI02bo= github.com/go-darwin/apfs v0.0.0-20211011131704-f84b94dbf348/go.mod h1:Czxo/d1g948LtrALAZdL04TL/HnkopquAjxYUuI02bo=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -299,8 +299,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM= github.com/go-openapi/errors v0.22.6 h1:eDxcf89O8odEnohIXwEjY1IB4ph5vmbUsBMsFNwXWPo=
github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= github.com/go-openapi/errors v0.22.6/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk=
github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ=
github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8=
github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=
@@ -311,14 +311,14 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
@@ -327,8 +327,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -386,12 +386,12 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
@@ -423,8 +423,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/internxt/rclone-adapter v0.0.0-20260130171252-c3c6ebb49276 h1:PTJPYovznNqc9t/9MjvtqhrgEVC9OiK75ZPL6hqm6gM= github.com/internxt/rclone-adapter v0.0.0-20260213125353-6f59c89fcb7c h1:r+KtxPyrhsYeNbsfeqTfEM8xRdwgV6LuNhLZxpXecb4=
github.com/internxt/rclone-adapter v0.0.0-20260130171252-c3c6ebb49276/go.mod h1:vdPya4AIcDjvng4ViaAzqjegJf0VHYpYHQguFx5xBp0= github.com/internxt/rclone-adapter v0.0.0-20260213125353-6f59c89fcb7c/go.mod h1:vdPya4AIcDjvng4ViaAzqjegJf0VHYpYHQguFx5xBp0=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -456,8 +456,8 @@ github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXw
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
@@ -490,8 +490,8 @@ github.com/lpar/date v1.0.0 h1:bq/zVqFTUmsxvd/CylidY4Udqpr9BOFrParoP6p0x/I=
github.com/lpar/date v1.0.0/go.mod h1:KjYe0dDyMQTgpqcUz4LEIeM5VZwhggjVx/V2dtc8NSo= github.com/lpar/date v1.0.0/go.mod h1:KjYe0dDyMQTgpqcUz4LEIeM5VZwhggjVx/V2dtc8NSo=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
@@ -499,8 +499,8 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ= github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4= github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
@@ -509,8 +509,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.97 h1:lqhREPyfgHTB/ciX8k2r8k0D93WaFqxbJX36UZq5occ= github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
github.com/minio/minio-go/v7 v7.0.97/go.mod h1:re5VXuo0pwEtoNLsNuSr0RrLfT/MBtohwdaSmPPSRSk= github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=
github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
github.com/minio/xxml v0.0.3 h1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU= github.com/minio/xxml v0.0.3 h1:ZIpPQpfyG5uZQnqqC0LZuWtPk/WT8G/qkxvO6jb7zMU=
@@ -527,8 +527,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncw/swift/v2 v2.0.5 h1:9o5Gsd7bInAFEqsGPcaUdsboMbqf8lnNtxqWKFT9iz8= github.com/ncw/swift/v2 v2.0.5 h1:9o5Gsd7bInAFEqsGPcaUdsboMbqf8lnNtxqWKFT9iz8=
github.com/ncw/swift/v2 v2.0.5/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk= github.com/ncw/swift/v2 v2.0.5/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk=
github.com/nwaples/rardecode/v2 v2.2.1 h1:DgHK/O/fkTQEKBJxBMC5d9IU8IgauifbpG78+rZJMnI= github.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=
github.com/nwaples/rardecode/v2 v2.2.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
@@ -537,12 +537,12 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU=
github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/oracle/oci-go-sdk/v65 v65.104.0 h1:l9awEvzWvxmYhy/97A0hZ87pa7BncYXmcO/S8+rvgK0= github.com/oracle/oci-go-sdk/v65 v65.108.2 h1:emoGAxw/vcqoKHgUy6a10RIhAQbaDPQPiuIcoZuoJGw=
github.com/oracle/oci-go-sdk/v65 v65.104.0/go.mod h1:oB8jFGVc/7/zJ+DbleE8MzGHjhs2ioCz5stRTdZdIcY= github.com/oracle/oci-go-sdk/v65 v65.108.2/go.mod h1:8ZzvzuEG/cFLFZhxg/Mg1w19KqyXBKO3c17QIc5PkGs=
github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= github.com/panjf2000/ants/v2 v2.11.5 h1:a7LMnMEeux/ebqTux140tRiaqcFTV0q2bEHF03nl6Rg=
github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/panjf2000/ants/v2 v2.11.5/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -553,8 +553,8 @@ github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
@@ -576,8 +576,8 @@ github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UH
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 h1:Y258uzXU/potCYnQd1r6wlAnoMB68BiCkCcCnKx1SH8= github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8 h1:Y258uzXU/potCYnQd1r6wlAnoMB68BiCkCcCnKx1SH8=
@@ -596,8 +596,8 @@ github.com/rclone/gofakes3 v0.0.4 h1:LswpC49VY/UJ1zucoL5ktnOEX6lq3qK7e1aFIAfqCbk
github.com/rclone/gofakes3 v0.0.4/go.mod h1:j/UoS+2/Mr7xAlfKhyVC58YyFQmh9uoQA5YZQXQUqmg= github.com/rclone/gofakes3 v0.0.4/go.mod h1:j/UoS+2/Mr7xAlfKhyVC58YyFQmh9uoQA5YZQXQUqmg=
github.com/relvacode/iso8601 v1.7.0 h1:BXy+V60stMP6cpswc+a93Mq3e65PfXCgDFfhvNNGrdo= github.com/relvacode/iso8601 v1.7.0 h1:BXy+V60stMP6cpswc+a93Mq3e65PfXCgDFfhvNNGrdo=
github.com/relvacode/iso8601 v1.7.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/relvacode/iso8601 v1.7.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
github.com/rfjakob/eme v1.1.2 h1:SxziR8msSOElPayZNFfQw4Tjx/Sbaeeh3eRvrHVMUs4= github.com/rfjakob/eme v1.2.0 h1:8dAHL+WVAw06+7DkRKnRiFp1JL3QjcJEZFqDnndUaSI=
github.com/rfjakob/eme v1.1.2/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k= github.com/rfjakob/eme v1.2.0/go.mod h1:cVvpasglm/G3ngEfcfT/Wt0GwhkuO32pf/poW6Nyk1k=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -607,7 +607,6 @@ github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
@@ -617,11 +616,11 @@ github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRo
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM= github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df h1:S77Pf5fIGMa7oSwp8SQPp7Hb4ZiI38K3RNBKD2LLeEM=
github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc= github.com/shabbyrobe/gocovmerge v0.0.0-20230507112040-c3350d9342df/go.mod h1:dcuzJZ83w/SqN9k4eQqwKYMgmKWzg/KzJAURBhRL1tc=
github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA= github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM= github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
@@ -638,8 +637,8 @@ github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368 h1:GyYC
github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368/go.mod h1:XkZYGzknZwkD0AKUnZaSXhRiVTLCkq7CWVa3IsE72gA= github.com/spacemonkeygo/monkit/v3 v3.0.25-0.20251022131615-eb24eb109368/go.mod h1:XkZYGzknZwkD0AKUnZaSXhRiVTLCkq7CWVa3IsE72gA=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@@ -659,15 +658,15 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/t3rm1n4l/go-mega v0.0.0-20251031123324-a804aaa87491 h1:rrGZv6xYk37hx0tW2sYfgbO0PqStbHqz6Bq6oc9Hurg= github.com/t3rm1n4l/go-mega v0.0.0-20251120131202-6845944c051c h1:dtcOwRimeiBFrlutmF6K94l0rxYFARNFMA+lSQ41C+M=
github.com/t3rm1n4l/go-mega v0.0.0-20251031123324-a804aaa87491/go.mod h1:ykucQyiE9Q2qx1wLlEtZkkNn1IURib/2O+Mvd25i1Fo= github.com/t3rm1n4l/go-mega v0.0.0-20251120131202-6845944c051c/go.mod h1:BF/l2jNyK+2h/BJZ7VLMAz6m/IWjA2F67gTjV1C/+Bo=
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tinylib/msgp v1.5.0 h1:GWnqAE54wmnlFazjq2+vgr736Akg58iiHImh+kPY2pc= github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
github.com/tinylib/msgp v1.5.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
@@ -712,12 +711,12 @@ github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU=
go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -725,26 +724,28 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=
goftp.io/server/v2 v2.0.2 h1:tkZpqyXys+vC15W5yGMi8Kzmbv1QSgeKr8qJXBnJbm8= goftp.io/server/v2 v2.0.2 h1:tkZpqyXys+vC15W5yGMi8Kzmbv1QSgeKr8qJXBnJbm8=
goftp.io/server/v2 v2.0.2/go.mod h1:Fl1WdcV7fx1pjOWx7jEHb7tsJ8VwE7+xHu6bVJ6r2qg= goftp.io/server/v2 v2.0.2/go.mod h1:Fl1WdcV7fx1pjOWx7jEHb7tsJ8VwE7+xHu6bVJ6r2qg=
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
@@ -764,8 +765,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -776,12 +777,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc=
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -794,8 +795,8 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20251021151156-188f512ec823 h1:M0DtBf/UvJoTH+tk6tgHT2NVxNEJCYhVu1g/xeD+GEk= golang.org/x/mobile v0.0.0-20260217195705-b56b3793a9c4 h1:uT3oYo9M38vJa7JpT4kCie2lJwOpoUrx7FvV0H7kXSc=
golang.org/x/mobile v0.0.0-20251021151156-188f512ec823/go.mod h1:3QSlP0AtP6HPTLbsxfgfefGN76jpIB9yBsMqB8UY37I= golang.org/x/mobile v0.0.0-20260217195705-b56b3793a9c4/go.mod h1:4OGHIUSBiIqyFAQDaX1tpY0BVnO20DvNDeATBu8aeFQ=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
@@ -808,8 +809,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -849,16 +850,16 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -874,8 +875,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -925,8 +926,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -938,8 +939,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -955,8 +956,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1010,8 +1011,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1034,8 +1035,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.255.0 h1:OaF+IbRwOottVCYV2wZan7KUq7UeNUQn1BcPc4K7lE4= google.golang.org/api v0.267.0 h1:w+vfWPMPYeRs8qH1aYYsFX68jMls5acWl/jocfLomwE=
google.golang.org/api v0.255.0/go.mod h1:d1/EtvCLdtiWEV4rAEHDHGh2bCnqsWhw+M8y2ECN4a8= google.golang.org/api v0.267.0/go.mod h1:Jzc0+ZfLnyvXma3UtaTl023TdhZu6OMBP9tJ+0EmFD0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1071,12 +1072,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d h1:t/LOSXPJ9R0B6fnZNyALBRfZBH0Uy0gT+uR+SJ6syqQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1089,8 +1090,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1101,8 +1102,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -1132,8 +1133,10 @@ moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHc
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
storj.io/common v0.0.0-20251107171817-6221ae45072c h1:UDXSrdeLJe3QFouavSW10fYdpclK0YNu3KvQHzqq2+k= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
storj.io/common v0.0.0-20251107171817-6221ae45072c/go.mod h1:XNX7uykja6aco92y2y8RuqaXIDRPpt1YA2OQDKlKEUk= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
storj.io/common v0.0.0-20260212175235-9580cc9c5777 h1:FkBDQhObVplAc6Ug4SnQDfq4zpw8d3zFy19Zrc3hTvI=
storj.io/common v0.0.0-20260212175235-9580cc9c5777/go.mod h1:XNX7uykja6aco92y2y8RuqaXIDRPpt1YA2OQDKlKEUk=
storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55 h1:8OE12DvUnB9lfZcHe7IDGsuhjrY9GBAr964PVHmhsro= storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55 h1:8OE12DvUnB9lfZcHe7IDGsuhjrY9GBAr964PVHmhsro=
storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55/go.mod h1:Y9LZaa8esL1PW2IDMqJE7CFSNq7d5bQ3RI7mGPtmKMg= storj.io/drpc v0.0.35-0.20250513201419-f7819ea69b55/go.mod h1:Y9LZaa8esL1PW2IDMqJE7CFSNq7d5bQ3RI7mGPtmKMg=
storj.io/eventkit v0.0.0-20250410172343-61f26d3de156 h1:5MZ0CyMbG6Pi0rRzUWVG6dvpXjbBYEX2oyXuj+tT+sk= storj.io/eventkit v0.0.0-20250410172343-61f26d3de156 h1:5MZ0CyMbG6Pi0rRzUWVG6dvpXjbBYEX2oyXuj+tT+sk=

View File

@@ -44,7 +44,7 @@ func Walk(err error, f WalkFunc) {
// *os.SyscallError and many others in the stdlib. // *os.SyscallError and many others in the stdlib.
errType := reflect.TypeOf(err) errType := reflect.TypeOf(err)
errValue := reflect.ValueOf(err) errValue := reflect.ValueOf(err)
if errValue.IsValid() && errType.Kind() == reflect.Ptr { if errValue.IsValid() && errType.Kind() == reflect.Pointer {
errType = errType.Elem() errType = errType.Elem()
errValue = errValue.Elem() errValue = errValue.Elem()
} }

View File

@@ -159,18 +159,30 @@ func (p *Pacer) beginCall(limitConnections bool) {
// XXX ms later we put another in. We could do this with a // XXX ms later we put another in. We could do this with a
// Ticker more accurately, but then we'd have to work out how // Ticker more accurately, but then we'd have to work out how
// not to run it when it wasn't needed // not to run it when it wasn't needed
<-p.pacer
p.mu.Lock()
sleepTime := p.state.SleepTime
p.mu.Unlock()
if sleepTime > 0 {
<-p.pacer
// Re-read the sleep time as it may be stale
// after waiting for the pacer token
p.mu.Lock()
sleepTime = p.state.SleepTime
p.mu.Unlock()
// Restart the timer
go func(t time.Duration) {
time.Sleep(t)
p.pacer <- struct{}{}
}(sleepTime)
}
if limitConnections { if limitConnections {
<-p.connTokens <-p.connTokens
} }
p.mu.Lock()
// Restart the timer
go func(t time.Duration) {
time.Sleep(t)
p.pacer <- struct{}{}
}(p.state.SleepTime)
p.mu.Unlock()
} }
// endCall implements the pacing algorithm // endCall implements the pacing algorithm

View File

@@ -367,6 +367,39 @@ func TestCallMaxConnectionsRecursiveDeadlock(t *testing.T) {
assert.Equal(t, errFoo, err) assert.Equal(t, errFoo, err)
} }
func TestCallMaxConnectionsRecursiveDeadlock2(t *testing.T) {
p := New(CalculatorOption(NewDefault(MinSleep(1*time.Millisecond), MaxSleep(2*time.Millisecond))))
p.SetMaxConnections(1)
dp := &dummyPaced{retry: false}
wg := new(sync.WaitGroup)
// Normal
for range 100 {
wg.Go(func() {
err := p.Call(func() (bool, error) {
// check we have taken the connection token
assert.Equal(t, 0, len(p.connTokens))
return false, nil
})
assert.NoError(t, err)
})
// Now attempt a recursive call
wg.Go(func() {
err := p.Call(func() (bool, error) {
// check we have taken the connection token
assert.Equal(t, 0, len(p.connTokens))
// Do recursive call
return false, p.Call(dp.fn)
})
assert.Equal(t, errFoo, err)
})
}
// Tidy up
wg.Wait()
}
func TestRetryAfterError_NonNilErr(t *testing.T) { func TestRetryAfterError_NonNilErr(t *testing.T) {
orig := errors.New("test failure") orig := errors.New("test failure")
dur := 2 * time.Second dur := 2 * time.Second

View File

@@ -290,9 +290,7 @@ func TestPoolMaxBufferMemory(t *testing.T) {
) )
const trials = 50 const trials = 50
for i := range trials { for i := range trials {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
if i < trials/2 { if i < trials/2 {
n := i%4 + 1 n := i%4 + 1
buf := bp.GetN(n) buf := bp.GetN(n)
@@ -307,7 +305,7 @@ func TestPoolMaxBufferMemory(t *testing.T) {
countBuf(-1) countBuf(-1)
bp.Put(buf) bp.Put(buf)
} }
}() })
} }
wg.Wait() wg.Wait()

View File

@@ -563,11 +563,9 @@ func TestRWConcurrency(t *testing.T) {
writeTo := func(rw *RW, size int64) { writeTo := func(rw *RW, size int64) {
in, out := io.Pipe() in, out := io.Pipe()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
check(in, size, rw) check(in, size, rw)
}() })
var n int64 var n int64
for n < size { for n < size {
nn, err := rw.WriteTo(out) nn, err := rw.WriteTo(out)

View File

@@ -16,7 +16,6 @@ import (
"net/http" "net/http"
"net/textproto" "net/textproto"
"net/url" "net/url"
"strings"
"sync" "sync"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
@@ -374,26 +373,11 @@ func (api *Client) Call(ctx context.Context, opts *Opts) (resp *http.Response, e
return resp, nil return resp, nil
} }
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string {
return quoteEscaper.Replace(s)
}
// multipartFileContentDisposition returns the value of a Content-Disposition header
// with the provided field name and file name.
func multipartFileContentDisposition(fieldname, filename string) string {
return fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
escapeQuotes(fieldname), escapeQuotes(filename))
}
// CreateFormFile is a convenience wrapper around [Writer.CreatePart]. It creates // CreateFormFile is a convenience wrapper around [Writer.CreatePart]. It creates
// a new form-data header with the provided field name and file name. // a new form-data header with the provided field name and file name.
func CreateFormFile(w *multipart.Writer, fieldname, filename, contentType string) (io.Writer, error) { func CreateFormFile(w *multipart.Writer, fieldname, filename, contentType string) (io.Writer, error) {
h := make(textproto.MIMEHeader) h := make(textproto.MIMEHeader)
// FIXME when go1.24 is no longer supported, change to h.Set("Content-Disposition", multipart.FileContentDisposition(fieldname, filename))
// multipart.FileContentDisposition and remove definition above
h.Set("Content-Disposition", multipartFileContentDisposition(fieldname, filename))
if contentType != "" { if contentType != "" {
h.Set("Content-Type", contentType) h.Set("Content-Type", contentType)
} }

View File

@@ -296,13 +296,11 @@ func main() {
quit = make(chan struct{}, *iterations) quit = make(chan struct{}, *iterations)
) )
for range *number { for range *number {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
t := NewTest(dir) t := NewTest(dir)
defer t.Tidy() defer t.Tidy()
t.RandomTests(*iterations, quit) t.RandomTests(*iterations, quit)
}() })
} }
wg.Wait() wg.Wait()
} }

View File

@@ -115,9 +115,7 @@ func New(item Item, opt *vfscommon.Options, remote string, src fs.Object) (dls *
src: src, src: src,
remote: remote, remote: remote,
} }
dls.wg.Add(1) dls.wg.Go(func() {
go func() {
defer dls.wg.Done()
ticker := time.NewTicker(backgroundKickerInterval) ticker := time.NewTicker(backgroundKickerInterval)
select { select {
case <-ticker.C: case <-ticker.C:
@@ -129,7 +127,7 @@ func New(item Item, opt *vfscommon.Options, remote string, src fs.Object) (dls *
break break
} }
ticker.Stop() ticker.Stop()
}() })
return dls return dls
} }
@@ -189,9 +187,7 @@ func (dls *Downloaders) _newDownloader(r ranges.Range) (dl *downloader, err erro
dls.dls = append(dls.dls, dl) dls.dls = append(dls.dls, dl)
dl.wg.Add(1) dl.wg.Go(func() {
go func() {
defer dl.wg.Done()
n, err := dl.download() n, err := dl.download()
_ = dl.close(err) _ = dl.close(err)
dl.dls.countErrors(n, err) dl.dls.countErrors(n, err)
@@ -202,7 +198,7 @@ func (dls *Downloaders) _newDownloader(r ranges.Range) (dl *downloader, err erro
if err != nil { if err != nil {
fs.Errorf(dl.dls.src, "vfs cache: failed to kick waiters: %v", err) fs.Errorf(dl.dls.src, "vfs cache: failed to kick waiters: %v", err)
} }
}() })
return dl, nil return dl, nil
} }

View File

@@ -542,9 +542,7 @@ func TestItemReadWrite(t *testing.T) {
assert.False(t, item.present()) assert.False(t, item.present())
var wg sync.WaitGroup var wg sync.WaitGroup
for range 8 { for range 8 {
wg.Add(1) wg.Go(func() {
go func() {
defer wg.Done()
in := readers.NewPatternReader(size) in := readers.NewPatternReader(size)
buf := make([]byte, 1024*1024) buf := make([]byte, 1024*1024)
buf2 := make([]byte, 1024*1024) buf2 := make([]byte, 1024*1024)
@@ -553,7 +551,7 @@ func TestItemReadWrite(t *testing.T) {
offset := max(rand.Int63n(size+2*int64(blockSize))-int64(blockSize), 0) offset := max(rand.Int63n(size+2*int64(blockSize))-int64(blockSize), 0)
_, _ = readCheckBuf(t, in, buf, buf2, item, offset, blockSize) _, _ = readCheckBuf(t, in, buf, buf2, item, offset, blockSize)
} }
}() })
} }
wg.Wait() wg.Wait()
require.NoError(t, item.Close(nil)) require.NoError(t, item.Close(nil))