mirror of
https://github.com/rclone/rclone.git
synced 2026-02-16 16:58:57 +00:00
Compare commits
13 Commits
darthShado
...
fix-dropbo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c741c02fb6 | ||
|
|
56e8d75cab | ||
|
|
e74f5b8906 | ||
|
|
e4a7686444 | ||
|
|
c3884aafd9 | ||
|
|
0a9785a4ff | ||
|
|
8140f67092 | ||
|
|
4a001b8a02 | ||
|
|
525433e6dd | ||
|
|
f71f6c57d7 | ||
|
|
e35623c72e | ||
|
|
344bce7e2a | ||
|
|
3a4322a7ba |
11
Makefile
11
Makefile
@@ -8,7 +8,8 @@ VERSION := $(shell cat VERSION)
|
|||||||
# Last tag on this branch
|
# Last tag on this branch
|
||||||
LAST_TAG := $(shell git describe --tags --abbrev=0)
|
LAST_TAG := $(shell git describe --tags --abbrev=0)
|
||||||
# Next version
|
# Next version
|
||||||
NEXT_VERSION := $(shell echo $(VERSION) | perl -lpe 's/v//; $$_ += 0.01; $$_ = sprintf("v%.2f.0", $$_)')
|
NEXT_VERSION := $(shell echo $(VERSION) | awk -F. -v OFS=. '{print $$1,$$2+1,0}')
|
||||||
|
NEXT_PATCH_VERSION := $(shell echo $(VERSION) | awk -F. -v OFS=. '{print $$1,$$2,$$3+1}')
|
||||||
# If we are working on a release, override branch to master
|
# If we are working on a release, override branch to master
|
||||||
ifdef RELEASE_TAG
|
ifdef RELEASE_TAG
|
||||||
BRANCH := master
|
BRANCH := master
|
||||||
@@ -246,5 +247,13 @@ startdev:
|
|||||||
echo "$(NEXT_VERSION)" > VERSION
|
echo "$(NEXT_VERSION)" > VERSION
|
||||||
git commit -m "Start $(NEXT_VERSION)-DEV development" fs/version.go VERSION docs/layouts/partials/version.html
|
git commit -m "Start $(NEXT_VERSION)-DEV development" fs/version.go VERSION docs/layouts/partials/version.html
|
||||||
|
|
||||||
|
startstable:
|
||||||
|
@echo "Version is $(VERSION)"
|
||||||
|
@echo "Next stable version is $(NEXT_PATCH_VERSION)"
|
||||||
|
echo -e "package fs\n\n// Version of rclone\nvar Version = \"$(NEXT_PATCH_VERSION)-DEV\"\n" | gofmt > fs/version.go
|
||||||
|
echo -n "$(NEXT_PATCH_VERSION)" > docs/layouts/partials/version.html
|
||||||
|
echo "$(NEXT_PATCH_VERSION)" > VERSION
|
||||||
|
git commit -m "Start $(NEXT_PATCH_VERSION)-DEV development" fs/version.go VERSION docs/layouts/partials/version.html
|
||||||
|
|
||||||
winzip:
|
winzip:
|
||||||
zip -9 rclone-$(TAG).zip rclone.exe
|
zip -9 rclone-$(TAG).zip rclone.exe
|
||||||
|
|||||||
57
RELEASE.md
57
RELEASE.md
@@ -9,7 +9,7 @@ This file describes how to make the various kinds of releases
|
|||||||
|
|
||||||
## Making a release
|
## Making a release
|
||||||
|
|
||||||
* git checkout master
|
* git checkout master # see below for stable branch
|
||||||
* git pull
|
* git pull
|
||||||
* git status - make sure everything is checked in
|
* git status - make sure everything is checked in
|
||||||
* Check GitHub actions build for master is Green
|
* Check GitHub actions build for master is Green
|
||||||
@@ -31,7 +31,7 @@ This file describes how to make the various kinds of releases
|
|||||||
* make upload
|
* make upload
|
||||||
* make upload_website
|
* make upload_website
|
||||||
* make upload_github
|
* make upload_github
|
||||||
* make startdev
|
* make startdev # make startstable for stable branch
|
||||||
* # announce with forum post, twitter post, patreon post
|
* # announce with forum post, twitter post, patreon post
|
||||||
|
|
||||||
Early in the next release cycle update the dependencies
|
Early in the next release cycle update the dependencies
|
||||||
@@ -42,62 +42,35 @@ Early in the next release cycle update the dependencies
|
|||||||
* git add new files
|
* git add new files
|
||||||
* git commit -a -v
|
* git commit -a -v
|
||||||
|
|
||||||
If `make update` fails with errors like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
# github.com/cpuguy83/go-md2man/md2man
|
|
||||||
../../../../pkg/mod/github.com/cpuguy83/go-md2man@v1.0.8/md2man/md2man.go:11:16: undefined: blackfriday.EXTENSION_NO_INTRA_EMPHASIS
|
|
||||||
../../../../pkg/mod/github.com/cpuguy83/go-md2man@v1.0.8/md2man/md2man.go:12:16: undefined: blackfriday.EXTENSION_TABLES
|
|
||||||
```
|
|
||||||
|
|
||||||
Can be fixed with
|
|
||||||
|
|
||||||
* GO111MODULE=on go get -u github.com/russross/blackfriday@v1.5.2
|
|
||||||
* GO111MODULE=on go mod tidy
|
|
||||||
|
|
||||||
|
|
||||||
## Making a point release
|
## Making a point release
|
||||||
|
|
||||||
If rclone needs a point release due to some horrendous bug:
|
If rclone needs a point release due to some horrendous bug:
|
||||||
|
|
||||||
First make the release branch. If this is a second point release then
|
Set vars
|
||||||
this will be done already.
|
|
||||||
|
|
||||||
* BASE_TAG=v1.XX # eg v1.52
|
* BASE_TAG=v1.XX # eg v1.52
|
||||||
* NEW_TAG=${BASE_TAG}.Y # eg v1.52.1
|
* NEW_TAG=${BASE_TAG}.Y # eg v1.52.1
|
||||||
* echo $BASE_TAG $NEW_TAG # v1.52 v1.52.1
|
* echo $BASE_TAG $NEW_TAG # v1.52 v1.52.1
|
||||||
|
|
||||||
|
First make the release branch. If this is a second point release then
|
||||||
|
this will be done already.
|
||||||
|
|
||||||
* git branch ${BASE_TAG} ${BASE_TAG}-stable
|
* git branch ${BASE_TAG} ${BASE_TAG}-stable
|
||||||
|
* git co ${BASE_TAG}-stable
|
||||||
|
* make startstable
|
||||||
|
|
||||||
Now
|
Now
|
||||||
|
|
||||||
* FIXME this is now broken with new semver layout - needs fixing
|
|
||||||
* FIXME the TAG=${NEW_TAG} shouldn't be necessary any more
|
|
||||||
* git co ${BASE_TAG}-stable
|
* git co ${BASE_TAG}-stable
|
||||||
* git cherry-pick any fixes
|
* git cherry-pick any fixes
|
||||||
* Test (see above)
|
* Do the steps as above
|
||||||
* make NEXT_VERSION=${NEW_TAG} tag
|
* make startstable
|
||||||
* edit docs/content/changelog.md
|
* NB this overwrites the current beta so we need to do this - FIXME is this true any more?
|
||||||
* make TAG=${NEW_TAG} doc
|
|
||||||
* git commit -a -v -m "Version ${NEW_TAG}"
|
|
||||||
* git tag -d ${NEW_TAG}
|
|
||||||
* git tag -s -m "Version ${NEW_TAG}" ${NEW_TAG}
|
|
||||||
* git push --tags -u origin ${BASE_TAG}-stable
|
|
||||||
* Wait for builds to complete
|
|
||||||
* make BRANCH_PATH= TAG=${NEW_TAG} fetch_binaries
|
|
||||||
* make TAG=${NEW_TAG} tarball
|
|
||||||
* make TAG=${NEW_TAG} sign_upload
|
|
||||||
* make TAG=${NEW_TAG} check_sign
|
|
||||||
* make TAG=${NEW_TAG} upload
|
|
||||||
* make TAG=${NEW_TAG} upload_website
|
|
||||||
* make TAG=${NEW_TAG} upload_github
|
|
||||||
* NB this overwrites the current beta so we need to do this
|
|
||||||
* git co master
|
* git co master
|
||||||
* make VERSION=${NEW_TAG} startdev
|
* # cherry pick the changes to the changelog
|
||||||
* # cherry pick the changes to the changelog and VERSION
|
* git checkout ${BASE_TAG}-stable docs/content/changelog.md
|
||||||
* git checkout ${BASE_TAG}-stable VERSION docs/content/changelog.md
|
* git commit -a -v -m "Changelog updates from Version ${NEW_TAG}"
|
||||||
* git commit --amend
|
|
||||||
* git push
|
* git push
|
||||||
* Announce!
|
|
||||||
|
|
||||||
## Making a manual build of docker
|
## Making a manual build of docker
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,17 @@ func driveScopesContainsAppFolder(scopes []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func driveOAuthOptions() []fs.Option {
|
||||||
|
opts := []fs.Option{}
|
||||||
|
for _, opt := range oauthutil.SharedOptions {
|
||||||
|
if opt.Name == config.ConfigClientID {
|
||||||
|
opt.Help = "Google Application Client Id\nSetting your own is recommended.\nSee https://rclone.org/drive/#making-your-own-client-id for how to create your own.\nIf you leave this blank, it will use an internal key which is low performance."
|
||||||
|
}
|
||||||
|
opts = append(opts, opt)
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
func init() {
|
func init() {
|
||||||
fs.Register(&fs.RegInfo{
|
fs.Register(&fs.RegInfo{
|
||||||
@@ -192,7 +203,7 @@ func init() {
|
|||||||
log.Fatalf("Failed to configure team drive: %v", err)
|
log.Fatalf("Failed to configure team drive: %v", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Options: append(oauthutil.SharedOptions, []fs.Option{{
|
Options: append(driveOAuthOptions(), []fs.Option{{
|
||||||
Name: "scope",
|
Name: "scope",
|
||||||
Help: "Scope that rclone should use when requesting access from drive.",
|
Help: "Scope that rclone should use when requesting access from drive.",
|
||||||
Examples: []fs.OptionExample{{
|
Examples: []fs.OptionExample{{
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ of path_display and all will be well.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -29,9 +30,11 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox"
|
||||||
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/async"
|
||||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/auth"
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/auth"
|
||||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/common"
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/common"
|
||||||
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
|
"github.com/dropbox/dropbox-sdk-go-unofficial/dropbox/files"
|
||||||
@@ -47,6 +50,7 @@ import (
|
|||||||
"github.com/rclone/rclone/fs/config/obscure"
|
"github.com/rclone/rclone/fs/config/obscure"
|
||||||
"github.com/rclone/rclone/fs/fserrors"
|
"github.com/rclone/rclone/fs/fserrors"
|
||||||
"github.com/rclone/rclone/fs/hash"
|
"github.com/rclone/rclone/fs/hash"
|
||||||
|
"github.com/rclone/rclone/lib/atexit"
|
||||||
"github.com/rclone/rclone/lib/encoder"
|
"github.com/rclone/rclone/lib/encoder"
|
||||||
"github.com/rclone/rclone/lib/oauthutil"
|
"github.com/rclone/rclone/lib/oauthutil"
|
||||||
"github.com/rclone/rclone/lib/pacer"
|
"github.com/rclone/rclone/lib/pacer"
|
||||||
@@ -61,6 +65,7 @@ const (
|
|||||||
minSleep = 10 * time.Millisecond
|
minSleep = 10 * time.Millisecond
|
||||||
maxSleep = 2 * time.Second
|
maxSleep = 2 * time.Second
|
||||||
decayConstant = 2 // bigger for slower decay, exponential
|
decayConstant = 2 // bigger for slower decay, exponential
|
||||||
|
maxBatchSize = 1000
|
||||||
// Upload chunk size - setting too small makes uploads slow.
|
// Upload chunk size - setting too small makes uploads slow.
|
||||||
// Chunks are buffered into memory for retries.
|
// Chunks are buffered into memory for retries.
|
||||||
//
|
//
|
||||||
@@ -142,6 +147,23 @@ memory. It can be set smaller if you are tight on memory.`, maxChunkSize),
|
|||||||
Help: "Impersonate this user when using a business account.",
|
Help: "Impersonate this user when using a business account.",
|
||||||
Default: "",
|
Default: "",
|
||||||
Advanced: true,
|
Advanced: true,
|
||||||
|
}, {
|
||||||
|
Name: "batch",
|
||||||
|
Help: `Enable batching of files if non-zero.
|
||||||
|
|
||||||
|
This sets the batch size of files to upload. It has to be less than 1000. A
|
||||||
|
sensible setting is probably 1000 if you are using this feature.
|
||||||
|
|
||||||
|
Rclone will close any outstanding batches when it exits.
|
||||||
|
|
||||||
|
Setting this is a great idea if you are uploading lots of small files as it will
|
||||||
|
make them a lot quicker. You can use --transfers 32 to maximise throughput.
|
||||||
|
|
||||||
|
It has the downside that rclone can't check the hash of the file after upload,
|
||||||
|
so using "rclone check" after the transfer completes is recommended.
|
||||||
|
`,
|
||||||
|
Default: 0,
|
||||||
|
Advanced: true,
|
||||||
}, {
|
}, {
|
||||||
Name: config.ConfigEncoding,
|
Name: config.ConfigEncoding,
|
||||||
Help: config.ConfigEncodingHelp,
|
Help: config.ConfigEncodingHelp,
|
||||||
@@ -163,6 +185,7 @@ memory. It can be set smaller if you are tight on memory.`, maxChunkSize),
|
|||||||
type Options struct {
|
type Options struct {
|
||||||
ChunkSize fs.SizeSuffix `config:"chunk_size"`
|
ChunkSize fs.SizeSuffix `config:"chunk_size"`
|
||||||
Impersonate string `config:"impersonate"`
|
Impersonate string `config:"impersonate"`
|
||||||
|
Batch int `config:"batch"`
|
||||||
Enc encoder.MultiEncoder `config:"encoding"`
|
Enc encoder.MultiEncoder `config:"encoding"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +203,7 @@ type Fs struct {
|
|||||||
slashRootSlash string // root with "/" prefix and postfix, lowercase
|
slashRootSlash string // root with "/" prefix and postfix, lowercase
|
||||||
pacer *fs.Pacer // To pace the API calls
|
pacer *fs.Pacer // To pace the API calls
|
||||||
ns string // The namespace we are using or "" for none
|
ns string // The namespace we are using or "" for none
|
||||||
|
batcher *batcher // batch builder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object describes a dropbox object
|
// Object describes a dropbox object
|
||||||
@@ -195,6 +219,165 @@ type Object struct {
|
|||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
|
// batcher holds info about the current items waiting for upload
|
||||||
|
type batcher struct {
|
||||||
|
f *Fs // Fs this batch is part of
|
||||||
|
mu sync.Mutex // lock for vars below
|
||||||
|
commitMu sync.Mutex // lock for waiting for batch
|
||||||
|
maxBatch int // maximum size for batch
|
||||||
|
active int // number of batches being sent
|
||||||
|
items []*files.UploadSessionFinishArg // current uncommitted files
|
||||||
|
atexit atexit.FnHandle // atexit handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBatcher creates a new batcher structure
|
||||||
|
func newBatcher(f *Fs, maxBatch int) *batcher {
|
||||||
|
return &batcher{
|
||||||
|
f: f,
|
||||||
|
maxBatch: maxBatch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts adding an item to a batch returning true if it was
|
||||||
|
// successfully started
|
||||||
|
//
|
||||||
|
// This should be paired with End
|
||||||
|
func (b *batcher) Start() bool {
|
||||||
|
if b.maxBatch <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.active++
|
||||||
|
// FIXME set a timer or something
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// End ends adding an item
|
||||||
|
func (b *batcher) End(started bool) error {
|
||||||
|
if !started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.active--
|
||||||
|
if len(b.items) < b.maxBatch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b._commit(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waits for the batch to complete - call with batchMu held
|
||||||
|
func (b *batcher) _waitForBatchResult(res *files.UploadSessionFinishBatchLaunch) (batchResult *files.UploadSessionFinishBatchResult, err error) {
|
||||||
|
if res.AsyncJobId == "" {
|
||||||
|
return res.Complete, nil
|
||||||
|
}
|
||||||
|
var batchStatus *files.UploadSessionFinishBatchJobStatus
|
||||||
|
sleepTime := time.Second
|
||||||
|
const maxTries = 120
|
||||||
|
for try := 1; try <= maxTries; try++ {
|
||||||
|
err = b.f.pacer.Call(func() (bool, error) {
|
||||||
|
batchStatus, err = b.f.srv.UploadSessionFinishBatchCheck(&async.PollArg{
|
||||||
|
AsyncJobId: res.AsyncJobId,
|
||||||
|
})
|
||||||
|
return shouldRetry(err)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fs.Errorf(b.f, "failed to wait for batch: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if batchStatus.Tag == "complete" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fs.Debugf(b.f, "sleeping for %v to wait for batch to complete, try %d/%d", sleepTime, try, maxTries)
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
}
|
||||||
|
return batchStatus.Complete, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit a batch - call with batchMu held
|
||||||
|
//
|
||||||
|
// if finalizing is true then it doesn't unregister Finalize as this
|
||||||
|
// causes a deadlock during finalization.
|
||||||
|
func (b *batcher) _commit(finalizing bool) (err error) {
|
||||||
|
b.commitMu.Lock()
|
||||||
|
batch := "batch"
|
||||||
|
if finalizing {
|
||||||
|
batch = "last batch"
|
||||||
|
}
|
||||||
|
fs.Debugf(b.f, "comitting %s length %d", batch, len(b.items))
|
||||||
|
var arg = &files.UploadSessionFinishBatchArg{
|
||||||
|
Entries: b.items,
|
||||||
|
}
|
||||||
|
var res *files.UploadSessionFinishBatchLaunch
|
||||||
|
err = b.f.pacer.Call(func() (bool, error) {
|
||||||
|
res, err = b.f.srv.UploadSessionFinishBatch(arg)
|
||||||
|
// If error is insufficient space then don't retry
|
||||||
|
if e, ok := err.(files.UploadSessionFinishAPIError); ok {
|
||||||
|
if e.EndpointError != nil && e.EndpointError.Path != nil && e.EndpointError.Path.Tag == files.WriteErrorInsufficientSpace {
|
||||||
|
err = fserrors.NoRetryError(err)
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// after the first chunk is uploaded, we retry everything
|
||||||
|
return err != nil, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
b.commitMu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear batch
|
||||||
|
b.items = nil
|
||||||
|
|
||||||
|
// If finalizing, don't unregister or get result
|
||||||
|
if finalizing {
|
||||||
|
b.commitMu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister the atexit since queue is empty
|
||||||
|
atexit.Unregister(b.atexit)
|
||||||
|
b.atexit = nil
|
||||||
|
|
||||||
|
// Wait for the batch to finish before we proceed in the background
|
||||||
|
go func() {
|
||||||
|
defer b.commitMu.Unlock()
|
||||||
|
_, err = b._waitForBatchResult(res)
|
||||||
|
if err != nil {
|
||||||
|
fs.Errorf(b.f, "Error waiting for batch to finish: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds a finished item to the batch
|
||||||
|
func (b *batcher) Add(commitInfo *files.UploadSessionFinishArg) {
|
||||||
|
fs.Debugf(b.f, "adding %q to batch", commitInfo.Commit.Path)
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
b.items = append(b.items, commitInfo)
|
||||||
|
if b.atexit == nil {
|
||||||
|
b.atexit = atexit.Register(b.Finalize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize finishes any pending batches
|
||||||
|
func (b *batcher) Finalize() {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if len(b.items) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := b._commit(true)
|
||||||
|
if err != nil {
|
||||||
|
fs.Errorf(b.f, "Failed to finalize last batch: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
|
||||||
// Name of the remote (as passed into NewFs)
|
// Name of the remote (as passed into NewFs)
|
||||||
func (f *Fs) Name() string {
|
func (f *Fs) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
@@ -230,7 +413,7 @@ func shouldRetry(err error) (bool, error) {
|
|||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case auth.RateLimitAPIError:
|
case auth.RateLimitAPIError:
|
||||||
if e.RateLimitError.RetryAfter > 0 {
|
if e.RateLimitError.RetryAfter > 0 {
|
||||||
fs.Debugf(baseErrString, "Too many requests or write operations. Trying again in %d seconds.", e.RateLimitError.RetryAfter)
|
fs.Logf(baseErrString, "Too many requests or write operations. Trying again in %d seconds.", e.RateLimitError.RetryAfter)
|
||||||
err = pacer.RetryAfterError(err, time.Duration(e.RateLimitError.RetryAfter)*time.Second)
|
err = pacer.RetryAfterError(err, time.Duration(e.RateLimitError.RetryAfter)*time.Second)
|
||||||
}
|
}
|
||||||
return true, err
|
return true, err
|
||||||
@@ -273,6 +456,9 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "dropbox: chunk size")
|
return nil, errors.Wrap(err, "dropbox: chunk size")
|
||||||
}
|
}
|
||||||
|
if opt.Batch > maxBatchSize || opt.Batch < 0 {
|
||||||
|
return nil, errors.Errorf("dropbox: batch must be < %d and >= 0 - it is currently %d", maxBatchSize, opt.Batch)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert the old token if it exists. The old token was just
|
// Convert the old token if it exists. The old token was just
|
||||||
// just a string, the new one is a JSON blob
|
// just a string, the new one is a JSON blob
|
||||||
@@ -297,6 +483,7 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
|||||||
opt: *opt,
|
opt: *opt,
|
||||||
pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
|
pacer: fs.NewPacer(pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
|
||||||
}
|
}
|
||||||
|
f.batcher = newBatcher(f, f.opt.Batch)
|
||||||
config := dropbox.Config{
|
config := dropbox.Config{
|
||||||
LogLevel: dropbox.LogOff, // logging in the SDK: LogOff, LogDebug, LogInfo
|
LogLevel: dropbox.LogOff, // logging in the SDK: LogOff, LogDebug, LogInfo
|
||||||
Client: oAuthClient, // maybe???
|
Client: oAuthClient, // maybe???
|
||||||
@@ -1044,6 +1231,13 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (in io.Read
|
|||||||
// unknown (i.e. -1) or smaller than uploadChunkSize, the method incurs an
|
// unknown (i.e. -1) or smaller than uploadChunkSize, the method incurs an
|
||||||
// avoidable request to the Dropbox API that does not carry payload.
|
// avoidable request to the Dropbox API that does not carry payload.
|
||||||
func (o *Object) uploadChunked(in0 io.Reader, commitInfo *files.CommitInfo, size int64) (entry *files.FileMetadata, err error) {
|
func (o *Object) uploadChunked(in0 io.Reader, commitInfo *files.CommitInfo, size int64) (entry *files.FileMetadata, err error) {
|
||||||
|
batching := o.fs.batcher.Start()
|
||||||
|
defer func() {
|
||||||
|
batchErr := o.fs.batcher.End(batching)
|
||||||
|
if err != nil {
|
||||||
|
err = batchErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
chunkSize := int64(o.fs.opt.ChunkSize)
|
chunkSize := int64(o.fs.opt.ChunkSize)
|
||||||
chunks := 0
|
chunks := 0
|
||||||
if size != -1 {
|
if size != -1 {
|
||||||
@@ -1057,11 +1251,15 @@ func (o *Object) uploadChunked(in0 io.Reader, commitInfo *files.CommitInfo, size
|
|||||||
fs.Debugf(o, "Streaming chunk %d/%d", cur, cur)
|
fs.Debugf(o, "Streaming chunk %d/%d", cur, cur)
|
||||||
} else if chunks == 0 {
|
} else if chunks == 0 {
|
||||||
fs.Debugf(o, "Streaming chunk %d/unknown", cur)
|
fs.Debugf(o, "Streaming chunk %d/unknown", cur)
|
||||||
} else {
|
} else if chunks != 1 {
|
||||||
fs.Debugf(o, "Uploading chunk %d/%d", cur, chunks)
|
fs.Debugf(o, "Uploading chunk %d/%d", cur, chunks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appendArg := files.UploadSessionAppendArg{
|
||||||
|
Close: chunks == 1,
|
||||||
|
}
|
||||||
|
|
||||||
// write the first chunk
|
// write the first chunk
|
||||||
fmtChunk(1, false)
|
fmtChunk(1, false)
|
||||||
var res *files.UploadSessionStartResult
|
var res *files.UploadSessionStartResult
|
||||||
@@ -1071,7 +1269,10 @@ func (o *Object) uploadChunked(in0 io.Reader, commitInfo *files.CommitInfo, size
|
|||||||
if _, err = chunk.Seek(0, io.SeekStart); err != nil {
|
if _, err = chunk.Seek(0, io.SeekStart); err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
res, err = o.fs.srv.UploadSessionStart(&files.UploadSessionStartArg{}, chunk)
|
arg := files.UploadSessionStartArg{
|
||||||
|
Close: appendArg.Close,
|
||||||
|
}
|
||||||
|
res, err = o.fs.srv.UploadSessionStart(&arg, chunk)
|
||||||
return shouldRetry(err)
|
return shouldRetry(err)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1082,22 +1283,34 @@ func (o *Object) uploadChunked(in0 io.Reader, commitInfo *files.CommitInfo, size
|
|||||||
SessionId: res.SessionId,
|
SessionId: res.SessionId,
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
}
|
}
|
||||||
appendArg := files.UploadSessionAppendArg{
|
appendArg.Cursor = &cursor
|
||||||
Cursor: &cursor,
|
|
||||||
Close: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// write more whole chunks (if any)
|
// write more whole chunks (if any, and if !batching), if
|
||||||
|
// batching write the last chunk also.
|
||||||
currentChunk := 2
|
currentChunk := 2
|
||||||
for {
|
for {
|
||||||
if chunks > 0 && currentChunk >= chunks {
|
if chunks > 0 {
|
||||||
// if the size is known, only upload full chunks. Remaining bytes are uploaded with
|
// Size known
|
||||||
// the UploadSessionFinish request.
|
if currentChunk == chunks {
|
||||||
break
|
// Last chunk
|
||||||
} else if chunks == 0 && in.BytesRead()-cursor.Offset < uint64(chunkSize) {
|
if !batching {
|
||||||
// if the size is unknown, upload as long as we can read full chunks from the reader.
|
// if the size is known, only upload full chunks. Remaining bytes are uploaded with
|
||||||
// The UploadSessionFinish request will not contain any payload.
|
// the UploadSessionFinish request.
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
appendArg.Close = true
|
||||||
|
} else if currentChunk > chunks {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Size unknown
|
||||||
|
lastReadWasShort := in.BytesRead()-cursor.Offset < uint64(chunkSize)
|
||||||
|
if lastReadWasShort {
|
||||||
|
// if the size is unknown, upload as long as we can read full chunks from the reader.
|
||||||
|
// The UploadSessionFinish request will not contain any payload.
|
||||||
|
// This is also what we want if batching
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cursor.Offset = in.BytesRead()
|
cursor.Offset = in.BytesRead()
|
||||||
fmtChunk(currentChunk, false)
|
fmtChunk(currentChunk, false)
|
||||||
@@ -1123,6 +1336,26 @@ func (o *Object) uploadChunked(in0 io.Reader, commitInfo *files.CommitInfo, size
|
|||||||
Cursor: &cursor,
|
Cursor: &cursor,
|
||||||
Commit: commitInfo,
|
Commit: commitInfo,
|
||||||
}
|
}
|
||||||
|
// If we are batching then we should have written all the data now
|
||||||
|
// store the commit info now for a batch commit
|
||||||
|
if batching {
|
||||||
|
// If we haven't closed the session then we need to
|
||||||
|
if !appendArg.Close {
|
||||||
|
fs.Debugf(o, "Closing session")
|
||||||
|
var empty bytes.Buffer
|
||||||
|
err = o.fs.pacer.Call(func() (bool, error) {
|
||||||
|
err = o.fs.srv.UploadSessionAppendV2(&appendArg, &empty)
|
||||||
|
// after the first chunk is uploaded, we retry everything
|
||||||
|
return err != nil, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.fs.batcher.Add(args)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
fmtChunk(currentChunk, true)
|
fmtChunk(currentChunk, true)
|
||||||
chunk = readers.NewRepeatableReaderBuffer(in, buf)
|
chunk = readers.NewRepeatableReaderBuffer(in, buf)
|
||||||
err = o.fs.pacer.Call(func() (bool, error) {
|
err = o.fs.pacer.Call(func() (bool, error) {
|
||||||
@@ -1165,7 +1398,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
|
|||||||
size := src.Size()
|
size := src.Size()
|
||||||
var err error
|
var err error
|
||||||
var entry *files.FileMetadata
|
var entry *files.FileMetadata
|
||||||
if size > int64(o.fs.opt.ChunkSize) || size == -1 {
|
if size > int64(o.fs.opt.ChunkSize) || size == -1 || o.fs.opt.Batch > 0 {
|
||||||
entry, err = o.uploadChunked(in, commitInfo, size)
|
entry, err = o.uploadChunked(in, commitInfo, size)
|
||||||
} else {
|
} else {
|
||||||
err = o.fs.pacer.CallNoRetry(func() (bool, error) {
|
err = o.fs.pacer.CallNoRetry(func() (bool, error) {
|
||||||
@@ -1176,6 +1409,13 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "upload failed")
|
return errors.Wrap(err, "upload failed")
|
||||||
}
|
}
|
||||||
|
// If we haven't received data back from batch upload then fake it
|
||||||
|
if entry == nil {
|
||||||
|
o.bytes = size
|
||||||
|
o.modTime = commitInfo.ClientModified
|
||||||
|
o.hash = "" // we don't have this
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return o.setMetadataFromEntry(entry)
|
return o.setMetadataFromEntry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ func stripVersion(goarch string) string {
|
|||||||
|
|
||||||
// build the binary in dir returning success or failure
|
// build the binary in dir returning success or failure
|
||||||
func compileArch(version, goos, goarch, dir string) bool {
|
func compileArch(version, goos, goarch, dir string) bool {
|
||||||
log.Printf("Compiling %s/%s", goos, goarch)
|
log.Printf("Compiling %s/%s into %s", goos, goarch, dir)
|
||||||
output := filepath.Join(dir, "rclone")
|
output := filepath.Join(dir, "rclone")
|
||||||
if goos == "windows" {
|
if goos == "windows" {
|
||||||
output += ".exe"
|
output += ".exe"
|
||||||
@@ -298,7 +298,6 @@ func compileArch(version, goos, goarch, dir string) bool {
|
|||||||
"go", "build",
|
"go", "build",
|
||||||
"--ldflags", "-s -X github.com/rclone/rclone/fs.Version=" + version,
|
"--ldflags", "-s -X github.com/rclone/rclone/fs.Version=" + version,
|
||||||
"-trimpath",
|
"-trimpath",
|
||||||
"-i",
|
|
||||||
"-o", output,
|
"-o", output,
|
||||||
"-tags", *tags,
|
"-tags", *tags,
|
||||||
"..",
|
"..",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
cmd.Root.AddCommand(commandDefinition)
|
cmd.Root.AddCommand(commandDefinition)
|
||||||
cmdFlags := commandDefinition.Flags()
|
cmdFlags := commandDefinition.Flags()
|
||||||
|
flags.BoolVarP(cmdFlags, &download, "download", "", download, "Check by downloading rather than with hash.")
|
||||||
AddFlags(cmdFlags)
|
AddFlags(cmdFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ the source match the files in the destination, not the other way
|
|||||||
around. This means that extra files in the destination that are not in
|
around. This means that extra files in the destination that are not in
|
||||||
the source will not be detected.
|
the source will not be detected.
|
||||||
|
|
||||||
The |--differ|, |--missing-on-dst|, |--missing-on-src|, |--src-only|
|
The |--differ|, |--missing-on-dst|, |--missing-on-src|, |--match|
|
||||||
and |--error| flags write paths, one per line, to the file name (or
|
and |--error| flags write paths, one per line, to the file name (or
|
||||||
stdout if it is |-|) supplied. What they write is described in the
|
stdout if it is |-|) supplied. What they write is described in the
|
||||||
help below. For example |--differ| will write all paths which are
|
help below. For example |--differ| will write all paths which are
|
||||||
|
|||||||
@@ -148,8 +148,13 @@ flag.
|
|||||||
Note that Jottacloud requires the MD5 hash before upload so if the
|
Note that Jottacloud requires the MD5 hash before upload so if the
|
||||||
source does not have an MD5 checksum then the file will be cached
|
source does not have an MD5 checksum then the file will be cached
|
||||||
temporarily on disk (wherever the `TMPDIR` environment variable points
|
temporarily on disk (wherever the `TMPDIR` environment variable points
|
||||||
to) before it is uploaded. Small files will be cached in memory - see
|
to) before it is uploaded. Small files will be cached in memory - see
|
||||||
the [--jottacloud-md5-memory-limit](#jottacloud-md5-memory-limit) flag.
|
the [--jottacloud-md5-memory-limit](#jottacloud-md5-memory-limit) flag.
|
||||||
|
When uploading from local disk the source checksum is always available,
|
||||||
|
so this does not apply. Starting with rclone version 1.52 the same is
|
||||||
|
true for crypted remotes (in older versions the crypt backend would not
|
||||||
|
calculate hashes for uploads from local disk, so the Jottacloud
|
||||||
|
backend had to do it as described above).
|
||||||
|
|
||||||
#### Restricted filename characters
|
#### Restricted filename characters
|
||||||
|
|
||||||
|
|||||||
@@ -537,6 +537,8 @@ OR
|
|||||||
"result": "<Raw command line output>"
|
"result": "<Raw command line output>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
**Authentication is required for this call.**
|
**Authentication is required for this call.**
|
||||||
|
|
||||||
### core/gc: Runs a garbage collection. {#core-gc}
|
### core/gc: Runs a garbage collection. {#core-gc}
|
||||||
@@ -1212,7 +1214,7 @@ This allows you to remove a plugin using it's name
|
|||||||
|
|
||||||
This takes parameters
|
This takes parameters
|
||||||
|
|
||||||
- name: name of the plugin in the format <author>/<plugin_name>
|
- name: name of the plugin in the format `author`/`plugin_name`
|
||||||
|
|
||||||
Eg
|
Eg
|
||||||
|
|
||||||
@@ -1226,7 +1228,7 @@ This allows you to remove a plugin using it's name
|
|||||||
|
|
||||||
This takes the following parameters
|
This takes the following parameters
|
||||||
|
|
||||||
- name: name of the plugin in the format <author>/<plugin_name>
|
- name: name of the plugin in the format `author`/`plugin_name`
|
||||||
|
|
||||||
Eg
|
Eg
|
||||||
|
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ func (s *StatsInfo) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(buf, "%s%10s / %s, %s, %s, ETA %s%s\n",
|
_, _ = fmt.Fprintf(buf, "%s%10s / %s, %s, %s, ETA %s%s",
|
||||||
dateString,
|
dateString,
|
||||||
fs.SizeSuffix(s.bytes),
|
fs.SizeSuffix(s.bytes),
|
||||||
fs.SizeSuffix(totalSize).Unit("Bytes"),
|
fs.SizeSuffix(totalSize).Unit("Bytes"),
|
||||||
@@ -283,6 +283,7 @@ func (s *StatsInfo) String() string {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if !fs.Config.StatsOneLine {
|
if !fs.Config.StatsOneLine {
|
||||||
|
_, _ = buf.WriteRune('\n')
|
||||||
errorDetails := ""
|
errorDetails := ""
|
||||||
switch {
|
switch {
|
||||||
case s.fatalError:
|
case s.fatalError:
|
||||||
@@ -291,6 +292,7 @@ func (s *StatsInfo) String() string {
|
|||||||
errorDetails = " (retrying may help)"
|
errorDetails = " (retrying may help)"
|
||||||
case s.errors != 0:
|
case s.errors != 0:
|
||||||
errorDetails = " (no need to retry)"
|
errorDetails = " (no need to retry)"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add only non zero stats
|
// Add only non zero stats
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ OR
|
|||||||
"result": "<Raw command line output>"
|
"result": "<Raw command line output>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
` + "```" + `
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func init() {
|
|||||||
|
|
||||||
This takes the following parameters
|
This takes the following parameters
|
||||||
|
|
||||||
- name: name of the plugin in the format <author>/<plugin_name>
|
- name: name of the plugin in the format ` + "`author`/`plugin_name`" + `
|
||||||
|
|
||||||
Eg
|
Eg
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ func init() {
|
|||||||
|
|
||||||
This takes parameters
|
This takes parameters
|
||||||
|
|
||||||
- name: name of the plugin in the format <author>/<plugin_name>
|
- name: name of the plugin in the format ` + "`author`/`plugin_name`" + `
|
||||||
|
|
||||||
Eg
|
Eg
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user