Merge branch 'rclone:master' into mct-install-set-modes-mac

This commit is contained in:
Michael C Tiernan - MIT-Research Computing Project 2022-05-19 08:22:38 -04:00 committed by GitHub
commit eab83baf6d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 194 additions and 103 deletions

View file

@ -245,18 +245,18 @@ retag:
startdev: startdev:
@echo "Version is $(VERSION)" @echo "Version is $(VERSION)"
@echo "Next version is $(NEXT_VERSION)" @echo "Next version is $(NEXT_VERSION)"
echo -e "package fs\n\n// Version of rclone\nvar Version = \"$(NEXT_VERSION)-DEV\"\n" | gofmt > fs/version.go echo -e "package fs\n\n// VersionTag of rclone\nvar VersionTag = \"$(NEXT_VERSION)\"\n" | gofmt > fs/versiontag.go
echo -n "$(NEXT_VERSION)" > docs/layouts/partials/version.html echo -n "$(NEXT_VERSION)" > docs/layouts/partials/version.html
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/versiontag.go VERSION docs/layouts/partials/version.html
startstable: startstable:
@echo "Version is $(VERSION)" @echo "Version is $(VERSION)"
@echo "Next stable version is $(NEXT_PATCH_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 -e "package fs\n\n// VersionTag of rclone\nvar VersionTag = \"$(NEXT_PATCH_VERSION)\"\n" | gofmt > fs/versiontag.go
echo -n "$(NEXT_PATCH_VERSION)" > docs/layouts/partials/version.html echo -n "$(NEXT_PATCH_VERSION)" > docs/layouts/partials/version.html
echo "$(NEXT_PATCH_VERSION)" > VERSION echo "$(NEXT_PATCH_VERSION)" > VERSION
git commit -m "Start $(NEXT_PATCH_VERSION)-DEV development" fs/version.go VERSION docs/layouts/partials/version.html git commit -m "Start $(NEXT_PATCH_VERSION)-DEV development" fs/versiontag.go VERSION docs/layouts/partials/version.html
winzip: winzip:
zip -9 rclone-$(TAG).zip rclone.exe zip -9 rclone-$(TAG).zip rclone.exe

View file

@ -515,7 +515,7 @@ func (f *Fs) setChunkNameFormat(pattern string) error {
strRegex := regexp.QuoteMeta(pattern) strRegex := regexp.QuoteMeta(pattern)
strRegex = reHashes.ReplaceAllLiteralString(strRegex, reDataOrCtrl) strRegex = reHashes.ReplaceAllLiteralString(strRegex, reDataOrCtrl)
strRegex = strings.Replace(strRegex, "\\*", mainNameRegStr, -1) strRegex = strings.ReplaceAll(strRegex, "\\*", mainNameRegStr)
strRegex = fmt.Sprintf("^%s(?:%s|%s)?$", strRegex, tempSuffixRegStr, tempSuffixRegOld) strRegex = fmt.Sprintf("^%s(?:%s|%s)?$", strRegex, tempSuffixRegStr, tempSuffixRegOld)
f.nameRegexp = regexp.MustCompile(strRegex) f.nameRegexp = regexp.MustCompile(strRegex)
@ -524,7 +524,7 @@ func (f *Fs) setChunkNameFormat(pattern string) error {
if numDigits > 1 { if numDigits > 1 {
fmtDigits = fmt.Sprintf("%%0%dd", numDigits) fmtDigits = fmt.Sprintf("%%0%dd", numDigits)
} }
strFmt := strings.Replace(pattern, "%", "%%", -1) strFmt := strings.ReplaceAll(pattern, "%", "%%")
strFmt = strings.Replace(strFmt, "*", "%s", 1) strFmt = strings.Replace(strFmt, "*", "%s", 1)
f.dataNameFmt = reHashes.ReplaceAllLiteralString(strFmt, fmtDigits) f.dataNameFmt = reHashes.ReplaceAllLiteralString(strFmt, fmtDigits)
f.ctrlNameFmt = reHashes.ReplaceAllLiteralString(strFmt, "_%s") f.ctrlNameFmt = reHashes.ReplaceAllLiteralString(strFmt, "_%s")

View file

@ -70,7 +70,7 @@ const (
// 1<<18 is the minimum size supported by the Google uploader, and there is no maximum. // 1<<18 is the minimum size supported by the Google uploader, and there is no maximum.
minChunkSize = fs.SizeSuffix(googleapi.MinUploadChunkSize) minChunkSize = fs.SizeSuffix(googleapi.MinUploadChunkSize)
defaultChunkSize = 8 * fs.Mebi defaultChunkSize = 8 * fs.Mebi
partialFields = "id,name,size,md5Checksum,trashed,explicitlyTrashed,modifiedTime,createdTime,mimeType,parents,webViewLink,shortcutDetails,exportLinks" partialFields = "id,name,size,md5Checksum,trashed,explicitlyTrashed,modifiedTime,createdTime,mimeType,parents,webViewLink,shortcutDetails,exportLinks,resourceKey"
listRGrouping = 50 // number of IDs to search at once when using ListR listRGrouping = 50 // number of IDs to search at once when using ListR
listRInputBuffer = 1000 // size of input buffer when using ListR listRInputBuffer = 1000 // size of input buffer when using ListR
defaultXDGIcon = "text-html" defaultXDGIcon = "text-html"
@ -660,6 +660,7 @@ type baseObject struct {
mimeType string // The object MIME type mimeType string // The object MIME type
bytes int64 // size of the object bytes int64 // size of the object
parents []string // IDs of the parent directories parents []string // IDs of the parent directories
resourceKey *string // resourceKey is needed for link shared objects
} }
type documentObject struct { type documentObject struct {
baseObject baseObject
@ -829,8 +830,8 @@ func (f *Fs) list(ctx context.Context, dirIDs []string, title string, directorie
if title != "" { if title != "" {
searchTitle := f.opt.Enc.FromStandardName(title) searchTitle := f.opt.Enc.FromStandardName(title)
// Escaping the backslash isn't documented but seems to work // Escaping the backslash isn't documented but seems to work
searchTitle = strings.Replace(searchTitle, `\`, `\\`, -1) searchTitle = strings.ReplaceAll(searchTitle, `\`, `\\`)
searchTitle = strings.Replace(searchTitle, `'`, `\'`, -1) searchTitle = strings.ReplaceAll(searchTitle, `'`, `\'`)
var titleQuery bytes.Buffer var titleQuery bytes.Buffer
_, _ = fmt.Fprintf(&titleQuery, "(name='%s'", searchTitle) _, _ = fmt.Fprintf(&titleQuery, "(name='%s'", searchTitle)
@ -1319,12 +1320,16 @@ func (f *Fs) newRegularObject(remote string, info *drive.File) fs.Object {
} }
} }
} }
return &Object{ o := &Object{
baseObject: f.newBaseObject(remote, info), baseObject: f.newBaseObject(remote, info),
url: fmt.Sprintf("%sfiles/%s?alt=media", f.svc.BasePath, actualID(info.Id)), url: fmt.Sprintf("%sfiles/%s?alt=media", f.svc.BasePath, actualID(info.Id)),
md5sum: strings.ToLower(info.Md5Checksum), md5sum: strings.ToLower(info.Md5Checksum),
v2Download: f.opt.V2DownloadMinSize != -1 && info.Size >= int64(f.opt.V2DownloadMinSize), v2Download: f.opt.V2DownloadMinSize != -1 && info.Size >= int64(f.opt.V2DownloadMinSize),
} }
if info.ResourceKey != "" {
o.resourceKey = &info.ResourceKey
}
return o
} }
// newDocumentObject creates an fs.Object for a google docs drive.File // newDocumentObject creates an fs.Object for a google docs drive.File
@ -2429,11 +2434,12 @@ func (f *Fs) Copy(ctx context.Context, src fs.Object, remote string) (fs.Object,
var info *drive.File var info *drive.File
err = f.pacer.Call(func() (bool, error) { err = f.pacer.Call(func() (bool, error) {
info, err = f.svc.Files.Copy(id, createInfo). copy := f.svc.Files.Copy(id, createInfo).
Fields(partialFields). Fields(partialFields).
SupportsAllDrives(true). SupportsAllDrives(true).
KeepRevisionForever(f.opt.KeepRevisionForever). KeepRevisionForever(f.opt.KeepRevisionForever)
Context(ctx).Do() srcObj.addResourceKey(copy.Header())
info, err = copy.Context(ctx).Do()
return f.shouldRetry(ctx, err) return f.shouldRetry(ctx, err)
}) })
if err != nil { if err != nil {
@ -3530,6 +3536,14 @@ func (o *baseObject) Storable() bool {
return true return true
} }
// addResourceKey adds a X-Goog-Drive-Resource-Keys header for this
// object if required.
func (o *baseObject) addResourceKey(header http.Header) {
if o.resourceKey != nil {
header.Add("X-Goog-Drive-Resource-Keys", fmt.Sprintf("%s/%s", o.id, *o.resourceKey))
}
}
// httpResponse gets an http.Response object for the object // httpResponse gets an http.Response object for the object
// using the url and method passed in // using the url and method passed in
func (o *baseObject) httpResponse(ctx context.Context, url, method string, options []fs.OpenOption) (req *http.Request, res *http.Response, err error) { func (o *baseObject) httpResponse(ctx context.Context, url, method string, options []fs.OpenOption) (req *http.Request, res *http.Response, err error) {
@ -3545,6 +3559,7 @@ func (o *baseObject) httpResponse(ctx context.Context, url, method string, optio
// Don't supply range requests for 0 length objects as they always fail // Don't supply range requests for 0 length objects as they always fail
delete(req.Header, "Range") delete(req.Header, "Range")
} }
o.addResourceKey(req.Header)
err = o.fs.pacer.Call(func() (bool, error) { err = o.fs.pacer.Call(func() (bool, error) {
res, err = o.fs.client.Do(req) res, err = o.fs.client.Do(req)
if err == nil { if err == nil {

View file

@ -562,7 +562,7 @@ func (f *Fs) list(ctx context.Context, filter api.SearchFilter, fn listFn) (err
for i := range items { for i := range items {
item := &result.MediaItems[i] item := &result.MediaItems[i]
remote := item.Filename remote := item.Filename
remote = strings.Replace(remote, "/", "", -1) remote = strings.ReplaceAll(remote, "/", "")
err = fn(remote, item, false) err = fn(remote, item, false)
if err != nil { if err != nil {
return err return err

View file

@ -119,7 +119,7 @@ func (f *Fs) getCredentials(ctx context.Context) (err error) {
defer fs.CheckClose(resp.Body, &err) defer fs.CheckClose(resp.Body, &err)
if resp.StatusCode < 200 || resp.StatusCode > 299 { if resp.StatusCode < 200 || resp.StatusCode > 299 {
body, _ := ioutil.ReadAll(resp.Body) body, _ := ioutil.ReadAll(resp.Body)
bodyStr := strings.TrimSpace(strings.Replace(string(body), "\n", " ", -1)) bodyStr := strings.TrimSpace(strings.ReplaceAll(string(body), "\n", " "))
return fmt.Errorf("failed to get credentials: %s: %s", resp.Status, bodyStr) return fmt.Errorf("failed to get credentials: %s: %s", resp.Status, bodyStr)
} }
decoder := json.NewDecoder(resp.Body) decoder := json.NewDecoder(resp.Body)

View file

@ -191,7 +191,7 @@ machines.`)
m.Set("auth_code", "") m.Set("auth_code", "")
return fs.ConfigGoto("legacy_do_auth") return fs.ConfigGoto("legacy_do_auth")
case "legacy_auth_code": case "legacy_auth_code":
authCode := strings.Replace(config.Result, "-", "", -1) // remove any "-" contained in the code so we have a 6 digit number authCode := strings.ReplaceAll(config.Result, "-", "") // remove any "-" contained in the code so we have a 6 digit number
m.Set("auth_code", authCode) m.Set("auth_code", authCode)
return fs.ConfigGoto("legacy_do_auth") return fs.ConfigGoto("legacy_do_auth")
case "legacy_do_auth": case "legacy_do_auth":
@ -649,7 +649,7 @@ func errorHandler(resp *http.Response) error {
// Jottacloud wants '+' to be URL encoded even though the RFC states it's not reserved // Jottacloud wants '+' to be URL encoded even though the RFC states it's not reserved
func urlPathEscape(in string) string { func urlPathEscape(in string) string {
return strings.Replace(rest.URLPathEscape(in), "+", "%2B", -1) return strings.ReplaceAll(rest.URLPathEscape(in), "+", "%2B")
} }
// filePathRaw returns an unescaped file path (f.root, file) // filePathRaw returns an unescaped file path (f.root, file)

View file

@ -1294,7 +1294,7 @@ For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview
Note that this ACL is applied when server-side copying objects as S3 Note that this ACL is applied when server-side copying objects as S3
doesn't copy the ACL from the source but rather writes a fresh one.`, doesn't copy the ACL from the source but rather writes a fresh one.`,
Provider: "!Storj", Provider: "!Storj,Cloudflare",
Examples: []fs.OptionExample{{ Examples: []fs.OptionExample{{
Value: "default", Value: "default",
Help: "Owner gets Full_CONTROL.\nNo one else has access rights (default).", Help: "Owner gets Full_CONTROL.\nNo one else has access rights (default).",
@ -2966,7 +2966,7 @@ func (f *Fs) Precision() time.Duration {
// pathEscape escapes s as for a URL path. It uses rest.URLPathEscape // pathEscape escapes s as for a URL path. It uses rest.URLPathEscape
// but also escapes '+' for S3 and Digital Ocean spaces compatibility // but also escapes '+' for S3 and Digital Ocean spaces compatibility
func pathEscape(s string) string { func pathEscape(s string) string {
return strings.Replace(rest.URLPathEscape(s), "+", "%2B", -1) return strings.ReplaceAll(rest.URLPathEscape(s), "+", "%2B")
} }
// copy does a server-side copy // copy does a server-side copy

View file

@ -1311,7 +1311,7 @@ var shellEscapeRegex = regexp.MustCompile("[^A-Za-z0-9_.,:/\\@\u0080-\uFFFFFFFF\
// when sending it to a shell. // when sending it to a shell.
func shellEscape(str string) string { func shellEscape(str string) string {
safe := shellEscapeRegex.ReplaceAllString(str, `\$0`) safe := shellEscapeRegex.ReplaceAllString(str, `\$0`)
return strings.Replace(safe, "\n", "'\n'", -1) return strings.ReplaceAll(safe, "\n", "'\n'")
} }
// Converts a byte array from the SSH session returned by // Converts a byte array from the SSH session returned by

View file

@ -173,8 +173,8 @@ func buildZip(dir string) string {
func buildDebAndRpm(dir, version, goarch string) []string { func buildDebAndRpm(dir, version, goarch string) []string {
// Make internal version number acceptable to .deb and .rpm // Make internal version number acceptable to .deb and .rpm
pkgVersion := version[1:] pkgVersion := version[1:]
pkgVersion = strings.Replace(pkgVersion, "β", "-beta", -1) pkgVersion = strings.ReplaceAll(pkgVersion, "β", "-beta")
pkgVersion = strings.Replace(pkgVersion, "-", ".", -1) pkgVersion = strings.ReplaceAll(pkgVersion, "-", ".")
nfpmArch, ok := goarchToNfpm[goarch] nfpmArch, ok := goarchToNfpm[goarch]
if !ok { if !ok {
nfpmArch = goarch nfpmArch = goarch

View file

@ -79,7 +79,7 @@ rclone.org website.`,
var description = map[string]string{} var description = map[string]string{}
var addDescription func(root *cobra.Command) var addDescription func(root *cobra.Command)
addDescription = func(root *cobra.Command) { addDescription = func(root *cobra.Command) {
name := strings.Replace(root.CommandPath(), " ", "_", -1) + ".md" name := strings.ReplaceAll(root.CommandPath(), " ", "_") + ".md"
description[name] = root.Short description[name] = root.Short
for _, c := range root.Commands() { for _, c := range root.Commands() {
addDescription(c) addDescription(c)
@ -93,11 +93,11 @@ rclone.org website.`,
base := strings.TrimSuffix(name, path.Ext(name)) base := strings.TrimSuffix(name, path.Ext(name))
data := frontmatter{ data := frontmatter{
Date: now, Date: now,
Title: strings.Replace(base, "_", " ", -1), Title: strings.ReplaceAll(base, "_", " "),
Description: description[name], Description: description[name],
Slug: base, Slug: base,
URL: "/commands/" + strings.ToLower(base) + "/", URL: "/commands/" + strings.ToLower(base) + "/",
Source: strings.Replace(strings.Replace(base, "rclone", "cmd", -1), "_", "/", -1) + "/", Source: strings.ReplaceAll(strings.ReplaceAll(base, "rclone", "cmd"), "_", "/") + "/",
} }
var buf bytes.Buffer var buf bytes.Buffer
err := frontmatterTemplate.Execute(&buf, data) err := frontmatterTemplate.Execute(&buf, data)

View file

@ -290,7 +290,7 @@ func list(ctx context.Context) error {
if !ok { if !ok {
return errors.New("bad JSON") return errors.New("bad JSON")
} }
fmt.Printf("### %s: %s {#%s}\n\n", info["Path"], info["Title"], strings.Replace(info["Path"].(string), "/", "-", -1)) fmt.Printf("### %s: %s {#%s}\n\n", info["Path"], info["Title"], strings.ReplaceAll(info["Path"].(string), "/", "-"))
fmt.Printf("%s\n\n", info["Help"]) fmt.Printf("%s\n\n", info["Help"])
if authRequired := info["AuthRequired"]; authRequired != nil { if authRequired := info["AuthRequired"]; authRequired != nil {
if authRequired.(bool) { if authRequired.(bool) {

View file

@ -43,7 +43,7 @@ var shellUnEscapeRegex = regexp.MustCompile(`\\(.)`)
// Unescape a string that was escaped by rclone // Unescape a string that was escaped by rclone
func shellUnEscape(str string) string { func shellUnEscape(str string) string {
str = strings.Replace(str, "'\n'", "\n", -1) str = strings.ReplaceAll(str, "'\n'", "\n")
str = shellUnEscapeRegex.ReplaceAllString(str, `$1`) str = shellUnEscapeRegex.ReplaceAllString(str, `$1`)
return str return str
} }

View file

@ -593,3 +593,6 @@ put them back in again.` >}}
* Kaspian <34658474+KaspianDev@users.noreply.github.com> * Kaspian <34658474+KaspianDev@users.noreply.github.com>
* Werner <EvilOlaf@users.noreply.github.com> * Werner <EvilOlaf@users.noreply.github.com>
* Hugal31 <hugo.laloge@gmail.com> * Hugal31 <hugo.laloge@gmail.com>
* Christian Galo <36752715+cgalo5758@users.noreply.github.com>
* Erik van Velzen <erik@evanv.nl>
* Derek Battams <derek@battams.ca>

View file

@ -3,11 +3,11 @@ title: "Install"
description: "Rclone Installation" description: "Rclone Installation"
--- ---
# Install # # Install
Rclone is a Go program and comes as a single binary file. Rclone is a Go program and comes as a single binary file.
## Quickstart ## ## Quickstart
* [Download](/downloads/) the relevant binary. * [Download](/downloads/) the relevant binary.
* Extract the `rclone` executable, `rclone.exe` on Windows, from the archive. * Extract the `rclone` executable, `rclone.exe` on Windows, from the archive.
@ -22,7 +22,7 @@ run `rclone -h`.
Already installed rclone can be easily updated to the latest version Already installed rclone can be easily updated to the latest version
using the [rclone selfupdate](/commands/rclone_selfupdate/) command. using the [rclone selfupdate](/commands/rclone_selfupdate/) command.
## Script installation ## ## Script installation
To install rclone on Linux/macOS/BSD systems, run: To install rclone on Linux/macOS/BSD systems, run:
@ -35,7 +35,7 @@ For beta installation, run:
Note that this script checks the version of rclone installed first and Note that this script checks the version of rclone installed first and
won't re-download if not needed. won't re-download if not needed.
## Linux installation from precompiled binary ## ## Linux installation from precompiled binary
Fetch and unpack Fetch and unpack
@ -59,7 +59,7 @@ Run `rclone config` to setup. See [rclone config docs](/docs/) for more details.
rclone config rclone config
## macOS installation with brew ## ## macOS installation with brew
brew install rclone brew install rclone
@ -68,7 +68,7 @@ NOTE: This version of rclone will not support `mount` any more (see
on macOS, either install a precompiled binary or enable the relevant option on macOS, either install a precompiled binary or enable the relevant option
when [installing from source](#install-from-source). when [installing from source](#install-from-source).
## macOS installation from precompiled binary, using curl ## ## macOS installation from precompiled binary, using curl
To avoid problems with macOS gatekeeper enforcing the binary to be signed and To avoid problems with macOS gatekeeper enforcing the binary to be signed and
notarized it is enough to download with `curl`. notarized it is enough to download with `curl`.
@ -96,20 +96,20 @@ Run `rclone config` to setup. See [rclone config docs](/docs/) for more details.
rclone config rclone config
## macOS installation from precompiled binary, using a web browser ## ## macOS installation from precompiled binary, using a web browser
When downloading a binary with a web browser, the browser will set the macOS When downloading a binary with a web browser, the browser will set the macOS
gatekeeper quarantine attribute. Starting from Catalina, when attempting to run gatekeeper quarantine attribute. Starting from Catalina, when attempting to run
`rclone`, a pop-up will appear saying: `rclone`, a pop-up will appear saying:
“rclone” cannot be opened because the developer cannot be verified. "rclone" cannot be opened because the developer cannot be verified.
macOS cannot verify that this app is free from malware. macOS cannot verify that this app is free from malware.
The simplest fix is to run The simplest fix is to run
xattr -d com.apple.quarantine rclone xattr -d com.apple.quarantine rclone
## Install with docker ## ## Install with docker
The rclone maintains a [docker image for rclone](https://hub.docker.com/r/rclone/rclone). The rclone maintains a [docker image for rclone](https://hub.docker.com/r/rclone/rclone).
These images are autobuilt by docker hub from the rclone source based These images are autobuilt by docker hub from the rclone source based
@ -188,39 +188,93 @@ ls ~/data/mount
kill %1 kill %1
``` ```
## Install from source ## ## Install from source
Make sure you have at least [Go](https://golang.org/) go1.16 Make sure you have git and [Go](https://golang.org/) installed.
installed. [Download go](https://golang.org/dl/) if necessary. The Go version 1.16 or newer is required, latest release is recommended.
latest release is recommended. Then 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:
```sh ```
git clone https://github.com/rclone/rclone.git git clone https://github.com/rclone/rclone.git
cd rclone cd rclone
go build go build
# If on macOS and mount is wanted, instead run: make GOTAGS=cmount
./rclone version
``` ```
This will leave you a checked out version of rclone you can modify and This will check out the rclone source in subfolder rclone, which you can later
send pull requests with. If you use `make` instead of `go build` then modify and send pull requests with. Then it will build the rclone executable
the rclone build will have the correct version information in it. in the same folder. As an initial check you can now run `./rclone version`
(`.\rclone version` on Windows).
You can also build the latest stable rclone with: Note that on macOS and Windows the [mount](https://rclone.org/commands/rclone_mount/)
command will not be available unless you specify additional build tag `cmount`.
```
go build -tags cmount
```
This assumes you have a GCC compatible C compiler (GCC or Clang) in your PATH,
as it uses [cgo](https://pkg.go.dev/cmd/cgo). But on Windows, the
[cgofuse](https://github.com/winfsp/cgofuse) library that the cmount
implementation is based on, also supports building
[without cgo](https://github.com/golang/go/wiki/WindowsDLLs), i.e. by setting
environment variable CGO_ENABLED to value 0 (static linking). This is how the
official Windows release of rclone is being built, starting with version 1.59.
It is still possible to build with cgo on Windows as well, by using the MinGW
port of GCC, e.g. by installing it in a [MSYS2](https://www.msys2.org)
distribution (make sure you install it in the classic mingw64 subsystem, the
ucrt64 version is not compatible).
Additionally, on Windows, you must install the third party utility
[WinFsp](http://www.secfs.net/winfsp/), with the "Developer" feature selected.
If building with cgo, you must also set environment variable CPATH pointing to
the fuse include directory within the WinFsp installation
(normally `C:\Program Files (x86)\WinFsp\inc\fuse`).
You may also add arguments `-ldflags -s` (with or without `-tags cmount`),
to omit symbol table and debug information, making the executable file smaller,
and `-trimpath` to remove references to local file system paths. This is how
the official rclone releases are built.
```
go build -trimpath -ldflags -s -tags cmount
```
Instead of executing the `go build` command directly, you can run it via the
Makefile, which also sets version information and copies the resulting rclone
executable into your GOPATH bin folder (`$(go env GOPATH)/bin`, which
corresponds to `~/go/bin/rclone` by default).
```
make
```
To include mount command on macOS and Windows with Makefile build:
```
make GOTAGS=cmount
```
As an alternative you can download the source, build and install rclone in one
operation, as a regular Go package. The source will be stored it in the Go
module cache, and the resulting executable will be in your GOPATH bin folder
(`$(go env GOPATH)/bin`, which corresponds to `~/go/bin/rclone` by default).
With Go version 1.17 or newer:
```
go install github.com/rclone/rclone@latest
```
With Go versions older than 1.17 (do **not** use the `-u` flag, it causes Go to
try to update the dependencies that rclone uses and sometimes these don't work
with the current version):
```
go get github.com/rclone/rclone go get github.com/rclone/rclone
```
or the latest version (equivalent to the beta) with ## Installation with Ansible
go get github.com/rclone/rclone@master
These will build the binary in `$(go env GOPATH)/bin`
(`~/go/bin/rclone` by default) after downloading the source to the go
module cache. Note - do **not** use the `-u` flag here. This causes go
to try to update the dependencies that rclone uses and sometimes these
don't work with the current version of rclone.
## Installation with Ansible ##
This can be done with [Stefan Weichinger's ansible This can be done with [Stefan Weichinger's ansible
role](https://github.com/stefangweichinger/ansible-rclone). role](https://github.com/stefangweichinger/ansible-rclone).
@ -236,7 +290,7 @@ Instructions
- rclone - rclone
``` ```
## Portable installation ## ## Portable installation
As mentioned [above](https://rclone.org/install/#quickstart), rclone is single As mentioned [above](https://rclone.org/install/#quickstart), rclone is single
executable (`rclone`, or `rclone.exe` on Windows) that you can download as a executable (`rclone`, or `rclone.exe` on Windows) that you can download as a
@ -314,7 +368,7 @@ the [PsExec](https://docs.microsoft.com/en-us/sysinternals/downloads/psexec)
utility from Microsoft's Sysinternals suite, which takes option `-s` to utility from Microsoft's Sysinternals suite, which takes option `-s` to
execute commands as the `SYSTEM` user. execute commands as the `SYSTEM` user.
#### Start from Startup folder ### #### Start from Startup folder
To quickly execute an rclone command you can simply create a standard To quickly execute an rclone command you can simply create a standard
Windows Explorer shortcut for the complete rclone command you want to run. If you Windows Explorer shortcut for the complete rclone command you want to run. If you
@ -329,7 +383,7 @@ functionality to set it to run as different user, or to set conditions or
actions on certain events. Setting up a scheduled task as described below actions on certain events. Setting up a scheduled task as described below
will often give you better results. will often give you better results.
#### Start from Task Scheduler ### #### Start from Task Scheduler
Task Scheduler is an administrative tool built into Windows, and it can be used to Task Scheduler is an administrative tool built into Windows, and it can be used to
configure rclone to be started automatically in a highly configurable way, e.g. configure rclone to be started automatically in a highly configurable way, e.g.
@ -339,14 +393,14 @@ be available to all users it can run as the `SYSTEM` user.
For technical information, see For technical information, see
https://docs.microsoft.com/windows/win32/taskschd/task-scheduler-start-page. https://docs.microsoft.com/windows/win32/taskschd/task-scheduler-start-page.
#### Run as service ### #### Run as service
For running rclone at system startup, you can create a Windows service that executes For running rclone at system startup, you can create a Windows service that executes
your rclone command, as an alternative to scheduled task configured to run at startup. your rclone command, as an alternative to scheduled task configured to run at startup.
##### Mount command built-in service integration #### ##### Mount command built-in service integration
For mount commands, Rclone has a built-in Windows service integration via the third-party For mount commands, rclone has a built-in Windows service integration via the third-party
WinFsp library it uses. Registering as a regular Windows service easy, as you just have to WinFsp library it uses. Registering as a regular Windows service easy, as you just have to
execute the built-in PowerShell command `New-Service` (requires administrative privileges). execute the built-in PowerShell command `New-Service` (requires administrative privileges).
@ -366,7 +420,7 @@ Windows standard methods for managing network drives. This is currently not
officially supported by Rclone, but with WinFsp version 2019.3 B2 / v1.5B2 or later officially supported by Rclone, but with WinFsp version 2019.3 B2 / v1.5B2 or later
it should be possible through path rewriting as described [here](https://github.com/rclone/rclone/issues/3340). it should be possible through path rewriting as described [here](https://github.com/rclone/rclone/issues/3340).
##### Third-party service integration ##### ##### Third-party service integration
To Windows service running any rclone command, the excellent third-party utility To Windows service running any rclone command, the excellent third-party utility
[NSSM](http://nssm.cc), the "Non-Sucking Service Manager", can be used. [NSSM](http://nssm.cc), the "Non-Sucking Service Manager", can be used.

View file

@ -150,6 +150,16 @@ use these methods. The alternative is to use `--rc-user` and
Default Off. Default Off.
### --rc-baseurl
Prefix for URLs.
Default is root
### --rc-template
User-specified template.
## Accessing the remote control via the rclone rc command {#api-rc} ## Accessing the remote control via the rclone rc command {#api-rc}
Rclone itself implements the remote control protocol in its `rclone Rclone itself implements the remote control protocol in its `rclone

View file

@ -2575,6 +2575,10 @@ Here is an example of making a Cloudflare R2 configuration. First run:
This will guide you through an interactive setup process. This will guide you through an interactive setup process.
Note that all buckets are private, and all are stored in the same
"auto" region. It is necessary to use Cloudflare workers to share the
content of a bucket publicly.
``` ```
No remotes found, make a new one? No remotes found, make a new one?
n) New remote n) New remote
@ -2631,19 +2635,6 @@ Endpoint for S3 API.
Required when using an S3 clone. Required when using an S3 clone.
Enter a value. Press Enter to leave empty. Enter a value. Press Enter to leave empty.
endpoint> https://ACCOUNT_ID.r2.cloudflarestorage.com endpoint> https://ACCOUNT_ID.r2.cloudflarestorage.com
Option acl.
Canned ACL used when creating buckets and storing or copying objects.
This ACL is used for creating objects and if bucket_acl isn't set, for creating buckets too.
For more info visit https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
Note that this ACL is applied when server-side copying objects as S3
doesn't copy the ACL from the source but rather writes a fresh one.
Choose a number from below, or type in your own value.
Press Enter to leave empty.
/ Owner gets FULL_CONTROL.
1 | No one else has access rights (default).
\ (private)
...
acl> 1
Edit advanced config? Edit advanced config?
y) Yes y) Yes
n) No (default) n) No (default)

View file

@ -230,7 +230,7 @@ func ConfigChoose(state string, name string, help string, n int, getItem func(i
// StatePush pushes a new values onto the front of the config string // StatePush pushes a new values onto the front of the config string
func StatePush(state string, values ...string) string { func StatePush(state string, values ...string) string {
for i := range values { for i := range values {
values[i] = strings.Replace(values[i], ",", "", -1) // replace comma with unicode wide version values[i] = strings.ReplaceAll(values[i], ",", "") // replace comma with unicode wide version
} }
if state != "" { if state != "" {
values = append(values[:len(values):len(values)], state) values = append(values[:len(values):len(values)], state)
@ -262,7 +262,7 @@ func StatePop(state string) (newState string, value string) {
return "", state return "", state
} }
value, newState = state[:comma], state[comma+1:] value, newState = state[:comma], state[comma+1:]
value = strings.Replace(value, "", ",", -1) // replace unicode wide comma with comma value = strings.ReplaceAll(value, "", ",") // replace unicode wide comma with comma
return newState, value return newState, value
} }

View file

@ -248,11 +248,11 @@ func AddConfig(ctx context.Context) (context.Context, *ConfigInfo) {
// "ignore-size") into an environment name // "ignore-size") into an environment name
// "RCLONE_CONFIG_MY-REMOTE_IGNORE_SIZE" // "RCLONE_CONFIG_MY-REMOTE_IGNORE_SIZE"
func ConfigToEnv(section, name string) string { func ConfigToEnv(section, name string) string {
return "RCLONE_CONFIG_" + strings.ToUpper(section+"_"+strings.Replace(name, "-", "_", -1)) return "RCLONE_CONFIG_" + strings.ToUpper(section+"_"+strings.ReplaceAll(name, "-", "_"))
} }
// OptionToEnv converts an option name, e.g. "ignore-size" into an // OptionToEnv converts an option name, e.g. "ignore-size" into an
// environment name "RCLONE_IGNORE_SIZE" // environment name "RCLONE_IGNORE_SIZE"
func OptionToEnv(name string) string { func OptionToEnv(name string) string {
return "RCLONE_" + strings.ToUpper(strings.Replace(name, "-", "_", -1)) return "RCLONE_" + strings.ToUpper(strings.ReplaceAll(name, "-", "_"))
} }

View file

@ -50,7 +50,7 @@ func setConfigFile(t *testing.T, data string) func() {
// toUnix converts \r\n to \n in buf // toUnix converts \r\n to \n in buf
func toUnix(buf string) string { func toUnix(buf string) string {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
return strings.Replace(buf, "\r\n", "\n", -1) return strings.ReplaceAll(buf, "\r\n", "\n")
} }
return buf return buf
} }

View file

@ -417,7 +417,7 @@ func ChooseOption(o *fs.Option, name string) string {
fmt.Printf("Option %s.\n", o.Name) fmt.Printf("Option %s.\n", o.Name)
if o.Help != "" { if o.Help != "" {
// Show help string without empty lines. // Show help string without empty lines.
help := strings.Replace(strings.TrimSpace(o.Help), "\n\n", "\n", -1) help := strings.ReplaceAll(strings.TrimSpace(o.Help), "\n\n", "\n")
fmt.Println(help) fmt.Println(help)
} }

View file

@ -208,7 +208,7 @@ loop:
value := path[prev : i-1] value := path[prev : i-1]
// replace any doubled quotes if there were any // replace any doubled quotes if there were any
if doubled { if doubled {
value = strings.Replace(value, string(quote)+string(quote), string(quote), -1) value = strings.ReplaceAll(value, string(quote)+string(quote), string(quote))
} }
prev = i + 1 prev = i + 1
parsed.Config[param] = value parsed.Config[param] = value

View file

@ -1407,7 +1407,7 @@ func TestDirMove(t *testing.T) {
require.NoError(t, operations.DirMove(ctx, r.Fremote, "A1", "A2")) require.NoError(t, operations.DirMove(ctx, r.Fremote, "A1", "A2"))
for i := range files { for i := range files {
files[i].Path = strings.Replace(files[i].Path, "A1/", "A2/", -1) files[i].Path = strings.ReplaceAll(files[i].Path, "A1/", "A2/")
} }
fstest.CheckListingWithPrecision( fstest.CheckListingWithPrecision(
@ -1432,7 +1432,7 @@ func TestDirMove(t *testing.T) {
require.NoError(t, operations.DirMove(ctx, r.Fremote, "A2", "A3")) require.NoError(t, operations.DirMove(ctx, r.Fremote, "A2", "A3"))
for i := range files { for i := range files {
files[i].Path = strings.Replace(files[i].Path, "A2/", "A3/", -1) files[i].Path = strings.ReplaceAll(files[i].Path, "A2/", "A3/")
} }
fstest.CheckListingWithPrecision( fstest.CheckListingWithPrecision(

View file

@ -44,7 +44,7 @@ type RegInfo struct {
// FileName returns the on disk file name for this backend // FileName returns the on disk file name for this backend
func (ri *RegInfo) FileName() string { func (ri *RegInfo) FileName() string {
return strings.Replace(ri.Name, " ", "", -1) return strings.ReplaceAll(ri.Name, " ", "")
} }
// Options is a slice of configuration Option for a backend // Options is a slice of configuration Option for a backend
@ -210,7 +210,7 @@ func (o *Option) Type() string {
// FlagName for the option // FlagName for the option
func (o *Option) FlagName(prefix string) string { func (o *Option) FlagName(prefix string) string {
name := strings.Replace(o.Name, "_", "-", -1) // convert snake_case to kebab-case name := strings.ReplaceAll(o.Name, "_", "-") // convert snake_case to kebab-case
if !o.NoPrefix { if !o.NoPrefix {
name = prefix + "-" + name name = prefix + "-" + name
} }

View file

@ -1,4 +1,14 @@
package fs package fs
// Version of rclone // Version of rclone containing the complete version string
var Version = "v1.59.0-DEV" var Version string
func init() {
if Version == "" {
if VersionSuffix == "" {
Version = VersionTag
} else {
Version = VersionTag + "-" + VersionSuffix
}
}
}

4
fs/versionsuffix.go Normal file
View file

@ -0,0 +1,4 @@
package fs
// VersionSuffix of rclone containing the pre-release label if any
var VersionSuffix = "DEV"

4
fs/versiontag.go Normal file
View file

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

View file

@ -317,7 +317,7 @@ func (r *Run) RemoveTestBinary() {
func (r *Run) Name() string { func (r *Run) Name() string {
ns := []string{ ns := []string{
r.Backend, r.Backend,
strings.Replace(r.Path, "/", ".", -1), strings.ReplaceAll(r.Path, "/", "."),
r.Remote, r.Remote,
} }
if r.FastList { if r.FastList {
@ -325,7 +325,7 @@ func (r *Run) Name() string {
} }
ns = append(ns, fmt.Sprintf("%d", r.Try)) ns = append(ns, fmt.Sprintf("%d", r.Try))
s := strings.Join(ns, "-") s := strings.Join(ns, "-")
s = strings.Replace(s, ":", "", -1) s = strings.ReplaceAll(s, ":", "")
return s return s
} }

View file

@ -30,7 +30,7 @@ func Add(fileName string, t time.Time) string {
base, ext := splitExt(fileName) base, ext := splitExt(fileName)
s := t.Format(versionFormat) s := t.Format(versionFormat)
// Replace the '.' with a '-' // Replace the '.' with a '-'
s = strings.Replace(s, ".", "-", -1) s = strings.ReplaceAll(s, ".", "-")
return base + s + ext return base + s + ext
} }

View file

@ -365,32 +365,32 @@ func rename(osOldPath, osNewPath string) error {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil return nil
} }
return fmt.Errorf("Failed to stat source: %s: %w", osOldPath, err) return fmt.Errorf("failed to stat source: %s: %w", osOldPath, err)
} }
if !sfi.Mode().IsRegular() { if !sfi.Mode().IsRegular() {
// cannot copy non-regular files (e.g., directories, symlinks, devices, etc.) // cannot copy non-regular files (e.g., directories, symlinks, devices, etc.)
return fmt.Errorf("Non-regular source file: %s (%q)", sfi.Name(), sfi.Mode().String()) return fmt.Errorf("non-regular source file: %s (%q)", sfi.Name(), sfi.Mode().String())
} }
dfi, err := os.Stat(osNewPath) dfi, err := os.Stat(osNewPath)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return fmt.Errorf("Failed to stat destination: %s: %w", osNewPath, err) return fmt.Errorf("failed to stat destination: %s: %w", osNewPath, err)
} }
parent := vfscommon.OsFindParent(osNewPath) parent := vfscommon.OSFindParent(osNewPath)
err = createDir(parent) err = createDir(parent)
if err != nil { if err != nil {
return fmt.Errorf("Failed to create parent dir: %s: %w", parent, err) return fmt.Errorf("failed to create parent dir: %s: %w", parent, err)
} }
} else { } else {
if !(dfi.Mode().IsRegular()) { if !(dfi.Mode().IsRegular()) {
return fmt.Errorf("Non-regular destination file: %s (%q)", dfi.Name(), dfi.Mode().String()) return fmt.Errorf("non-regular destination file: %s (%q)", dfi.Name(), dfi.Mode().String())
} }
if os.SameFile(sfi, dfi) { if os.SameFile(sfi, dfi) {
return nil return nil
} }
} }
if err = os.Rename(osOldPath, osNewPath); err != nil { if err = os.Rename(osOldPath, osNewPath); err != nil {
return fmt.Errorf("Failed to rename in cache: %s to %s: %w", osOldPath, osNewPath, err) return fmt.Errorf("failed to rename in cache: %s to %s: %w", osOldPath, osNewPath, err)
} }
return nil return nil
} }

View file

@ -5,11 +5,11 @@ import (
"path/filepath" "path/filepath"
) )
// OsFindParent returns the parent directory of name, or "" for the // OSFindParent returns the parent directory of name, or "" for the
// root for OS native paths. // root for OS native paths.
func OsFindParent(name string) string { func OSFindParent(name string) string {
parent := filepath.Dir(name) parent := filepath.Dir(name)
if parent == "." || parent == "/" { if parent == "." || (len(parent) == 1 && parent[0] == filepath.Separator) {
parent = "" parent = ""
} }
return parent return parent