mirror of
https://github.com/rclone/rclone.git
synced 2026-03-26 19:32:38 -04:00
This adds a go generate ./cmd/bisync command to autogenerate the bisync rc docs, including the list of params.
210 lines
7.9 KiB
Go
210 lines
7.9 KiB
Go
//go:generate go run help.go rc.md
|
|
package bisync
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"errors"
|
|
"log"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/rclone/rclone/cmd/bisync/bilib"
|
|
"github.com/rclone/rclone/fs"
|
|
fslog "github.com/rclone/rclone/fs/log"
|
|
"github.com/rclone/rclone/fs/rc"
|
|
)
|
|
|
|
func addRC() {
|
|
rc.Add(rc.Call{
|
|
Path: "sync/bisync",
|
|
AuthRequired: true,
|
|
Fn: rcBisync,
|
|
Title: shortHelp,
|
|
Help: rcHelp,
|
|
})
|
|
}
|
|
|
|
//go:embed rc.md
|
|
var rcHelp string
|
|
|
|
var shortHelp = `Perform bidirectional synchronization between two paths.`
|
|
|
|
var longHelp = shortHelp + MakeHelp(`
|
|
|
|
[Bisync](https://rclone.org/bisync/) provides a
|
|
bidirectional cloud sync solution in rclone.
|
|
It retains the Path1 and Path2 filesystem listings from the prior run.
|
|
On each successive run it will:
|
|
|
|
- list files on Path1 and Path2, and check for changes on each side.
|
|
Changes include ||New||, ||Newer||, ||Older||, and ||Deleted|| files.
|
|
- Propagate changes on Path1 to Path2, and vice-versa.
|
|
|
|
Bisync is considered an **advanced command**, so use with care.
|
|
Make sure you have read and understood the entire [manual](https://rclone.org/bisync)
|
|
(especially the [Limitations](https://rclone.org/bisync/#limitations) section)
|
|
before using, or data loss can result. Questions can be asked in the
|
|
[Rclone Forum](https://forum.rclone.org/).
|
|
|
|
See [full bisync description](https://rclone.org/bisync/) for details.
|
|
`)
|
|
|
|
// MakeHelp replaces some dynamic variables for the help docs
|
|
func MakeHelp(help string) string {
|
|
replacer := strings.NewReplacer(
|
|
"||", "`",
|
|
"{MAXDELETE}", strconv.Itoa(DefaultMaxDelete),
|
|
"{CHECKFILE}", DefaultCheckFilename,
|
|
"{WORKDIR}", DefaultWorkdir,
|
|
)
|
|
return replacer.Replace(help)
|
|
}
|
|
|
|
func rcBisync(ctx context.Context, in rc.Params) (out rc.Params, err error) {
|
|
opt := &Options{}
|
|
octx, ci := fs.AddConfig(ctx)
|
|
|
|
if dryRun, err := in.GetBool("dryRun"); err == nil {
|
|
ci.DryRun = dryRun
|
|
opt.DryRun = dryRun
|
|
} else if rc.NotErrParamNotFound(err) {
|
|
return nil, err
|
|
}
|
|
|
|
if maxDelete, err := in.GetInt64("maxDelete"); err == nil {
|
|
if maxDelete < 0 || maxDelete > 100 {
|
|
return nil, rc.NewErrParamInvalid(errors.New("maxDelete must be a percentage between 0 and 100"))
|
|
}
|
|
opt.MaxDelete = int(maxDelete)
|
|
} else if rc.NotErrParamNotFound(err) {
|
|
return nil, err
|
|
}
|
|
|
|
if opt.Resync, err = in.GetBool("resync"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("resync", "optional parameter is missing. using default value: %v", opt.Resync)
|
|
}
|
|
if opt.CheckAccess, err = in.GetBool("checkAccess"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("checkAccess", "optional parameter is missing. using default value: %v", opt.CheckAccess)
|
|
}
|
|
if opt.Force, err = in.GetBool("force"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("force", "optional parameter is missing. using default value: %v", opt.Force)
|
|
}
|
|
if opt.CreateEmptySrcDirs, err = in.GetBool("createEmptySrcDirs"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("createEmptySrcDirs", "optional parameter is missing. using default value: %v", opt.CreateEmptySrcDirs)
|
|
}
|
|
if opt.RemoveEmptyDirs, err = in.GetBool("removeEmptyDirs"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("removeEmptyDirs", "optional parameter is missing. using default value: %v", opt.RemoveEmptyDirs)
|
|
}
|
|
if opt.NoCleanup, err = in.GetBool("noCleanup"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("noCleanup", "optional parameter is missing. using default value: %v", opt.NoCleanup)
|
|
}
|
|
if opt.IgnoreListingChecksum, err = in.GetBool("ignoreListingChecksum"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("ignoreListingChecksum", "optional parameter is missing. using default value: %v", opt.IgnoreListingChecksum)
|
|
}
|
|
if opt.Resilient, err = in.GetBool("resilient"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("resilient", "optional parameter is missing. using default value: %v", opt.Resilient)
|
|
}
|
|
if opt.CheckFilename, err = in.GetString("checkFilename"); rc.NotErrParamNotFound(err) {
|
|
opt.CheckFilename = DefaultCheckFilename
|
|
fs.Debugf("checkFilename", "optional parameter is missing. using default value: %v", opt.CheckFilename)
|
|
}
|
|
if opt.FiltersFile, err = in.GetString("filtersFile"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("filtersFile", "optional parameter is missing. using default value: %v", opt.FiltersFile)
|
|
}
|
|
if opt.Workdir, err = in.GetString("workdir"); rc.NotErrParamNotFound(err) {
|
|
// "" sets correct default later
|
|
fs.Debugf("workdir", "optional parameter is missing. using default value: %v", opt.Workdir)
|
|
}
|
|
if opt.BackupDir1, err = in.GetString("backupDir1"); rc.NotErrParamNotFound(err) {
|
|
// we accept an alternate capitalization here for backward compatibility.
|
|
if opt.BackupDir1, err = in.GetString("backupdir1"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("backupDir1", "optional parameter is missing. using default value: %v", opt.BackupDir1)
|
|
}
|
|
}
|
|
if opt.BackupDir2, err = in.GetString("backupDir2"); rc.NotErrParamNotFound(err) {
|
|
// we accept an alternate capitalization here for backward compatibility.
|
|
if opt.BackupDir2, err = in.GetString("backupdir2"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("backupDir2", "optional parameter is missing. using default value: %v", opt.BackupDir2)
|
|
}
|
|
}
|
|
if err = setEnum(in, "checkSync", "true", opt.CheckSync.Set); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = setEnum(in, "resyncMode", opt.ResyncMode.String(), opt.ResyncMode.Set); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = setEnum(in, "conflictResolve", opt.ConflictResolve.String(), opt.ConflictResolve.Set); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = setEnum(in, "conflictLoser", opt.ConflictLoser.String(), opt.ConflictLoser.Set); err != nil {
|
|
return nil, err
|
|
}
|
|
if opt.ConflictSuffixFlag, err = in.GetString("conflictSuffix"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("conflictSuffix", "optional parameter is missing. using default value: %v", opt.ConflictSuffixFlag)
|
|
}
|
|
if opt.Recover, err = in.GetBool("recover"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("recover", "optional parameter is missing. using default value: %v", opt.Recover)
|
|
}
|
|
if opt.CompareFlag, err = in.GetString("compare"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("compare", "optional parameter is missing. using default value: %v", opt.CompareFlag)
|
|
}
|
|
if opt.Compare.NoSlowHash, err = in.GetBool("noSlowHash"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("noSlowHash", "optional parameter is missing. using default value: %v", opt.Compare.NoSlowHash)
|
|
}
|
|
if opt.Compare.SlowHashSyncOnly, err = in.GetBool("slowHashSyncOnly"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("slowHashSyncOnly", "optional parameter is missing. using default value: %v", opt.Compare.SlowHashSyncOnly)
|
|
}
|
|
if opt.Compare.DownloadHash, err = in.GetBool("downloadHash"); rc.NotErrParamNotFound(err) {
|
|
fs.Debugf("downloadHash", "optional parameter is missing. using default value: %v", opt.Compare.DownloadHash)
|
|
}
|
|
if opt.MaxLock, err = in.GetFsDuration("maxLock"); rc.NotErrParamNotFound(err) {
|
|
opt.MaxLock = 0
|
|
fs.Debugf("maxLock", "optional parameter is missing. using default value: %v", opt.MaxLock)
|
|
}
|
|
|
|
fs1, err := rc.GetFsNamed(octx, in, "path1")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fs2, err := rc.GetFsNamed(octx, in, "path2")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
output := bilib.CaptureOutput(func() {
|
|
err = Bisync(octx, fs1, fs2, opt)
|
|
})
|
|
|
|
workDir, _ := filepath.Abs(DefaultWorkdir)
|
|
if opt.Workdir != "" {
|
|
workDir, _ = filepath.Abs(opt.Workdir)
|
|
}
|
|
basePath := bilib.BasePath(ctx, workDir, fs1, fs2)
|
|
|
|
_, _ = log.Writer().Write(output)
|
|
return rc.Params{
|
|
"output": string(output),
|
|
"session": bilib.SessionName(fs1, fs2),
|
|
"workDir": workDir,
|
|
"basePath": basePath,
|
|
"listing1": basePath + ".path1.lst",
|
|
"listing2": basePath + ".path2.lst",
|
|
"logFile": fslog.Opt.File,
|
|
}, err
|
|
}
|
|
|
|
func setEnum(in rc.Params, name string, defaultVal string, set func(s string) error) error {
|
|
v, err := in.GetString(name)
|
|
if rc.NotErrParamNotFound(err) || v == "" {
|
|
v = defaultVal
|
|
fs.Debugf(name, "optional parameter is missing. using default value: %v", v)
|
|
}
|
|
if err := set(v); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|