2021-05-16 16:39:33 +00:00
// Package bisync implements bisync
// Copyright (c) 2017-2020 Chris Nelson
// Contributions to original python version: Hildo G. Jr., e2t, kalemas, silenceleaf
package bisync
import (
"context"
2021-11-04 10:12:57 +00:00
"errors"
"fmt"
2021-05-16 16:39:33 +00:00
"os"
"path/filepath"
"strconv"
gosync "sync"
"github.com/rclone/rclone/cmd/bisync/bilib"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/filter"
"github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/lib/atexit"
2023-11-06 11:59:41 +00:00
"github.com/rclone/rclone/lib/terminal"
2021-05-16 16:39:33 +00:00
)
// ErrBisyncAborted signals that bisync is aborted and forces exit code 2
var ErrBisyncAborted = errors . New ( "bisync aborted" )
// bisyncRun keeps bisync runtime state
type bisyncRun struct {
2023-10-06 20:38:47 +00:00
fs1 fs . Fs
fs2 fs . Fs
abort bool
critical bool
retryable bool
basePath string
workDir string
listing1 string
listing2 string
newListing1 string
newListing2 string
2023-11-09 10:04:33 +00:00
aliases bilib . AliasMap
2023-10-06 20:38:47 +00:00
opt * Options
2023-11-06 15:34:47 +00:00
octx context . Context
fctx context . Context
2021-05-16 16:39:33 +00:00
}
2023-10-01 13:36:19 +00:00
type queues struct {
copy1to2 bilib . Names
copy2to1 bilib . Names
renamed1 bilib . Names // renamed on 1 and copied to 2
renamed2 bilib . Names // renamed on 2 and copied to 1
renameSkipped bilib . Names // not renamed because it was equal
2023-10-07 10:33:43 +00:00
skippedDirs1 * fileList
skippedDirs2 * fileList
2023-10-01 13:36:19 +00:00
deletedonboth bilib . Names
}
2021-05-16 16:39:33 +00:00
// Bisync handles lock file, performs bisync run and checks exit status
func Bisync ( ctx context . Context , fs1 , fs2 fs . Fs , optArg * Options ) ( err error ) {
opt := * optArg // ensure that input is never changed
b := & bisyncRun {
fs1 : fs1 ,
fs2 : fs2 ,
opt : & opt ,
}
if opt . CheckFilename == "" {
opt . CheckFilename = DefaultCheckFilename
}
if opt . Workdir == "" {
opt . Workdir = DefaultWorkdir
}
if ! opt . DryRun && ! opt . Force {
if fs1 . Precision ( ) == fs . ModTimeNotSupported {
return errors . New ( "modification time support is missing on path1" )
}
if fs2 . Precision ( ) == fs . ModTimeNotSupported {
return errors . New ( "modification time support is missing on path2" )
}
}
if b . workDir , err = filepath . Abs ( opt . Workdir ) ; err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "failed to make workdir absolute: %w" , err )
2021-05-16 16:39:33 +00:00
}
if err = os . MkdirAll ( b . workDir , os . ModePerm ) ; err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "failed to create workdir: %w" , err )
2021-05-16 16:39:33 +00:00
}
// Produce a unique name for the sync operation
b . basePath = filepath . Join ( b . workDir , bilib . SessionName ( b . fs1 , b . fs2 ) )
2023-10-06 20:38:47 +00:00
b . listing1 = b . basePath + ".path1.lst"
b . listing2 = b . basePath + ".path2.lst"
b . newListing1 = b . listing1 + "-new"
b . newListing2 = b . listing2 + "-new"
2023-11-09 10:04:33 +00:00
b . aliases = bilib . AliasMap { }
2021-05-16 16:39:33 +00:00
// Handle lock file
lockFile := ""
if ! opt . DryRun {
lockFile = b . basePath + ".lck"
if bilib . FileExists ( lockFile ) {
2023-11-11 05:34:41 +00:00
errTip := Color ( terminal . MagentaFg , "Tip: this indicates that another bisync run (of these same paths) either is still running or was interrupted before completion. \n" )
errTip += Color ( terminal . MagentaFg , "If you're SURE you want to override this safety feature, you can delete the lock file with the following command, then run bisync again: \n" )
errTip += fmt . Sprintf ( Color ( terminal . HiRedFg , "rclone deletefile \"%s\"" ) , lockFile )
return fmt . Errorf ( Color ( terminal . RedFg , "prior lock file found: %s \n" ) + errTip , Color ( terminal . HiYellowFg , lockFile ) )
2021-05-16 16:39:33 +00:00
}
pidStr := [ ] byte ( strconv . Itoa ( os . Getpid ( ) ) )
2022-08-20 14:38:02 +00:00
if err = os . WriteFile ( lockFile , pidStr , bilib . PermSecure ) ; err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "cannot create lock file: %s: %w" , lockFile , err )
2021-05-16 16:39:33 +00:00
}
fs . Debugf ( nil , "Lock file created: %s" , lockFile )
}
// Handle SIGINT
var finaliseOnce gosync . Once
markFailed := func ( file string ) {
failFile := file + "-err"
if bilib . FileExists ( file ) {
_ = os . Remove ( failFile )
_ = os . Rename ( file , failFile )
}
}
finalise := func ( ) {
finaliseOnce . Do ( func ( ) {
if atexit . Signalled ( ) {
fs . Logf ( nil , "Bisync interrupted. Must run --resync to recover." )
2023-10-06 20:38:47 +00:00
markFailed ( b . listing1 )
markFailed ( b . listing2 )
2021-05-16 16:39:33 +00:00
_ = os . Remove ( lockFile )
}
} )
}
fnHandle := atexit . Register ( finalise )
defer atexit . Unregister ( fnHandle )
// run bisync
2023-10-06 20:38:47 +00:00
err = b . runLocked ( ctx )
2021-05-16 16:39:33 +00:00
if lockFile != "" {
errUnlock := os . Remove ( lockFile )
if errUnlock == nil {
fs . Debugf ( nil , "Lock file removed: %s" , lockFile )
} else if err == nil {
err = errUnlock
} else {
fs . Errorf ( nil , "cannot remove lockfile %s: %v" , lockFile , errUnlock )
}
}
if b . critical {
2023-07-11 10:57:49 +00:00
if b . retryable && b . opt . Resilient {
2023-11-06 11:59:41 +00:00
fs . Errorf ( nil , Color ( terminal . RedFg , "Bisync critical error: %v" ) , err )
fs . Errorf ( nil , Color ( terminal . YellowFg , "Bisync aborted. Error is retryable without --resync due to --resilient mode." ) )
2023-07-11 10:57:49 +00:00
} else {
2023-10-06 20:38:47 +00:00
if bilib . FileExists ( b . listing1 ) {
_ = os . Rename ( b . listing1 , b . listing1 + "-err" )
2023-07-11 11:09:06 +00:00
}
2023-10-06 20:38:47 +00:00
if bilib . FileExists ( b . listing2 ) {
_ = os . Rename ( b . listing2 , b . listing2 + "-err" )
2023-07-11 11:09:06 +00:00
}
2023-11-06 11:59:41 +00:00
fs . Errorf ( nil , Color ( terminal . RedFg , "Bisync critical error: %v" ) , err )
fs . Errorf ( nil , Color ( terminal . RedFg , "Bisync aborted. Must run --resync to recover." ) )
2023-07-11 10:57:49 +00:00
}
2021-05-16 16:39:33 +00:00
return ErrBisyncAborted
}
if b . abort {
2023-11-06 11:59:41 +00:00
fs . Logf ( nil , Color ( terminal . RedFg , "Bisync aborted. Please try again." ) )
2021-05-16 16:39:33 +00:00
}
if err == nil {
2023-11-06 11:59:41 +00:00
fs . Infof ( nil , Color ( terminal . GreenFg , "Bisync successful" ) )
2021-05-16 16:39:33 +00:00
}
return err
}
// runLocked performs a full bisync run
2023-10-06 20:38:47 +00:00
func ( b * bisyncRun ) runLocked ( octx context . Context ) ( err error ) {
2021-05-16 16:39:33 +00:00
opt := b . opt
path1 := bilib . FsPath ( b . fs1 )
path2 := bilib . FsPath ( b . fs2 )
if opt . CheckSync == CheckSyncOnly {
fs . Infof ( nil , "Validating listings for Path1 %s vs Path2 %s" , quotePath ( path1 ) , quotePath ( path2 ) )
2023-10-06 20:38:47 +00:00
if err = b . checkSync ( b . listing1 , b . listing2 ) ; err != nil {
2021-05-16 16:39:33 +00:00
b . critical = true
2023-07-11 10:57:49 +00:00
b . retryable = true
2021-05-16 16:39:33 +00:00
}
return err
}
fs . Infof ( nil , "Synching Path1 %s with Path2 %s" , quotePath ( path1 ) , quotePath ( path2 ) )
if opt . DryRun {
// In --dry-run mode, preserve original listings and save updates to the .lst-dry files
2023-10-06 20:38:47 +00:00
origListing1 := b . listing1
origListing2 := b . listing2
b . listing1 += "-dry"
b . listing2 += "-dry"
b . newListing1 = b . listing1 + "-new"
b . newListing2 = b . listing2 + "-new"
if err := bilib . CopyFileIfExists ( origListing1 , b . listing1 ) ; err != nil {
2021-05-16 16:39:33 +00:00
return err
}
2023-10-06 20:38:47 +00:00
if err := bilib . CopyFileIfExists ( origListing2 , b . listing2 ) ; err != nil {
2021-05-16 16:39:33 +00:00
return err
}
}
// Create second context with filters
var fctx context . Context
if fctx , err = b . opt . applyFilters ( octx ) ; err != nil {
b . critical = true
2023-07-11 10:57:49 +00:00
b . retryable = true
2021-05-16 16:39:33 +00:00
return
}
2023-11-06 15:34:47 +00:00
b . octx = octx
b . fctx = fctx
2021-05-16 16:39:33 +00:00
// Generate Path1 and Path2 listings and copy any unique Path2 files to Path1
if opt . Resync {
2023-10-06 20:38:47 +00:00
return b . resync ( octx , fctx )
2021-05-16 16:39:33 +00:00
}
// Check for existence of prior Path1 and Path2 listings
2023-10-06 20:38:47 +00:00
if ! bilib . FileExists ( b . listing1 ) || ! bilib . FileExists ( b . listing2 ) {
2021-05-16 16:39:33 +00:00
// On prior critical error abort, the prior listings are renamed to .lst-err to lock out further runs
b . critical = true
2023-07-11 10:57:49 +00:00
b . retryable = true
2023-11-11 05:34:41 +00:00
errTip := Color ( terminal . MagentaFg , "Tip: here are the filenames we were looking for. Do they exist? \n" )
errTip += fmt . Sprintf ( Color ( terminal . CyanFg , "Path1: %s\n" ) , Color ( terminal . HiBlueFg , b . listing1 ) )
errTip += fmt . Sprintf ( Color ( terminal . CyanFg , "Path2: %s\n" ) , Color ( terminal . HiBlueFg , b . listing2 ) )
errTip += Color ( terminal . MagentaFg , "Try running this command to inspect the work dir: \n" )
errTip += fmt . Sprintf ( Color ( terminal . HiCyanFg , "rclone lsl \"%s\"" ) , b . workDir )
return errors . New ( "cannot find prior Path1 or Path2 listings, likely due to critical error on prior run \n" + errTip )
2021-05-16 16:39:33 +00:00
}
2023-10-07 10:33:43 +00:00
fs . Infof ( nil , "Building Path1 and Path2 listings" )
ls1 , ls2 , err = b . makeMarchListing ( fctx )
if err != nil {
return err
}
2021-05-16 16:39:33 +00:00
// Check for Path1 deltas relative to the prior sync
fs . Infof ( nil , "Path1 checking for diffs" )
2023-10-07 10:33:43 +00:00
ds1 , err := b . findDeltas ( fctx , b . fs1 , b . listing1 , ls1 , "Path1" )
2021-05-16 16:39:33 +00:00
if err != nil {
return err
}
ds1 . printStats ( )
// Check for Path2 deltas relative to the prior sync
fs . Infof ( nil , "Path2 checking for diffs" )
2023-10-07 10:33:43 +00:00
ds2 , err := b . findDeltas ( fctx , b . fs2 , b . listing2 , ls2 , "Path2" )
2021-05-16 16:39:33 +00:00
if err != nil {
return err
}
ds2 . printStats ( )
// Check access health on the Path1 and Path2 filesystems
if opt . CheckAccess {
fs . Infof ( nil , "Checking access health" )
err = b . checkAccess ( ds1 . checkFiles , ds2 . checkFiles )
if err != nil {
b . critical = true
2023-07-11 10:57:49 +00:00
b . retryable = true
2021-05-16 16:39:33 +00:00
return
}
}
// Check for too many deleted files - possible error condition.
// Don't want to start deleting on the other side!
if ! opt . Force {
if ds1 . excessDeletes ( ) || ds2 . excessDeletes ( ) {
b . abort = true
return errors . New ( "too many deletes" )
}
}
// Check for all files changed such as all dates changed due to DST change
// to avoid errant copy everything.
if ! opt . Force {
msg := "Safety abort: all files were changed on %s %s. Run with --force if desired."
if ! ds1 . foundSame {
fs . Errorf ( nil , msg , ds1 . msg , quotePath ( path1 ) )
}
if ! ds2 . foundSame {
fs . Errorf ( nil , msg , ds2 . msg , quotePath ( path2 ) )
}
if ! ds1 . foundSame || ! ds2 . foundSame {
b . abort = true
return errors . New ( "all files were changed" )
}
}
// Determine and apply changes to Path1 and Path2
noChanges := ds1 . empty ( ) && ds2 . empty ( )
2023-10-01 13:36:19 +00:00
changes1 := false // 2to1
changes2 := false // 1to2
results2to1 := [ ] Results { }
results1to2 := [ ] Results { }
queues := queues { }
2021-05-16 16:39:33 +00:00
if noChanges {
fs . Infof ( nil , "No changes found" )
} else {
fs . Infof ( nil , "Applying changes" )
2023-10-01 13:36:19 +00:00
changes1 , changes2 , results2to1 , results1to2 , queues , err = b . applyDeltas ( octx , ds1 , ds2 )
2021-05-16 16:39:33 +00:00
if err != nil {
b . critical = true
2023-07-11 10:57:49 +00:00
// b.retryable = true // not sure about this one
2021-05-16 16:39:33 +00:00
return err
}
}
// Clean up and check listings integrity
fs . Infof ( nil , "Updating listings" )
var err1 , err2 error
2023-10-06 20:38:47 +00:00
b . saveOldListings ( )
// save new listings
2021-05-16 16:39:33 +00:00
if noChanges {
2023-10-07 10:33:43 +00:00
b . replaceCurrentListings ( )
2021-05-16 16:39:33 +00:00
} else {
2023-10-01 13:36:19 +00:00
if changes1 { // 2to1
2023-10-06 20:38:47 +00:00
err1 = b . modifyListing ( fctx , b . fs2 , b . fs1 , results2to1 , queues , false )
2021-05-16 16:39:33 +00:00
} else {
2023-10-06 20:38:47 +00:00
err1 = bilib . CopyFileIfExists ( b . newListing1 , b . listing1 )
2021-05-16 16:39:33 +00:00
}
2023-10-01 13:36:19 +00:00
if changes2 { // 1to2
2023-10-06 20:38:47 +00:00
err2 = b . modifyListing ( fctx , b . fs1 , b . fs2 , results1to2 , queues , true )
2021-05-16 16:39:33 +00:00
} else {
2023-10-06 20:38:47 +00:00
err2 = bilib . CopyFileIfExists ( b . newListing2 , b . listing2 )
2021-05-16 16:39:33 +00:00
}
}
err = err1
if err == nil {
err = err2
}
if err != nil {
b . critical = true
2023-07-11 10:57:49 +00:00
b . retryable = true
2021-05-16 16:39:33 +00:00
return err
}
if ! opt . NoCleanup {
2023-10-06 20:38:47 +00:00
_ = os . Remove ( b . newListing1 )
_ = os . Remove ( b . newListing2 )
2021-05-16 16:39:33 +00:00
}
if opt . CheckSync == CheckSyncTrue && ! opt . DryRun {
fs . Infof ( nil , "Validating listings for Path1 %s vs Path2 %s" , quotePath ( path1 ) , quotePath ( path2 ) )
2023-10-06 20:38:47 +00:00
if err := b . checkSync ( b . listing1 , b . listing2 ) ; err != nil {
2021-05-16 16:39:33 +00:00
b . critical = true
return err
}
}
// Optional rmdirs for empty directories
if opt . RemoveEmptyDirs {
fs . Infof ( nil , "Removing empty directories" )
err1 := operations . Rmdirs ( fctx , b . fs1 , "" , true )
err2 := operations . Rmdirs ( fctx , b . fs2 , "" , true )
err := err1
if err == nil {
err = err2
}
if err != nil {
b . critical = true
2023-07-11 10:57:49 +00:00
b . retryable = true
2021-05-16 16:39:33 +00:00
return err
}
}
return nil
}
// resync implements the --resync mode.
// It will generate path1 and path2 listings
// and copy any unique path2 files to path1.
2023-10-06 20:38:47 +00:00
func ( b * bisyncRun ) resync ( octx , fctx context . Context ) error {
2021-05-16 16:39:33 +00:00
fs . Infof ( nil , "Copying unique Path2 files to Path1" )
2023-11-06 15:34:47 +00:00
// Save blank filelists (will be filled from sync results)
var ls1 = newFileList ( )
var ls2 = newFileList ( )
err = ls1 . save ( fctx , b . newListing1 )
2021-05-16 16:39:33 +00:00
if err != nil {
2023-11-06 15:34:47 +00:00
b . abort = true
2021-05-16 16:39:33 +00:00
}
2023-11-06 15:34:47 +00:00
err = ls2 . save ( fctx , b . newListing2 )
2021-05-16 16:39:33 +00:00
if err != nil {
2023-11-06 15:34:47 +00:00
b . abort = true
2021-05-16 16:39:33 +00:00
}
2023-07-11 08:56:12 +00:00
// Check access health on the Path1 and Path2 filesystems
// enforce even though this is --resync
if b . opt . CheckAccess {
fs . Infof ( nil , "Checking access health" )
2023-11-06 15:34:47 +00:00
filesNow1 , filesNow2 , err := b . findCheckFiles ( fctx )
if err != nil {
b . critical = true
b . retryable = true
return err
}
2023-07-11 08:56:12 +00:00
ds1 := & deltaSet {
checkFiles : bilib . Names { } ,
}
ds2 := & deltaSet {
checkFiles : bilib . Names { } ,
}
for _ , file := range filesNow1 . list {
if filepath . Base ( file ) == b . opt . CheckFilename {
ds1 . checkFiles . Add ( file )
}
}
for _ , file := range filesNow2 . list {
if filepath . Base ( file ) == b . opt . CheckFilename {
ds2 . checkFiles . Add ( file )
}
}
err = b . checkAccess ( ds1 . checkFiles , ds2 . checkFiles )
if err != nil {
b . critical = true
2023-07-11 10:57:49 +00:00
b . retryable = true
2023-07-11 08:56:12 +00:00
return err
}
}
2023-10-01 13:36:19 +00:00
var results2to1 [ ] Results
var results1to2 [ ] Results
queues := queues { }
2021-05-16 16:39:33 +00:00
2023-11-06 15:34:47 +00:00
b . indent ( "Path2" , "Path1" , "Resync is copying UNIQUE files to" )
2021-05-16 16:39:33 +00:00
ctxRun := b . opt . setDryRun ( fctx )
// fctx has our extra filters added!
ctxSync , filterSync := filter . AddConfig ( ctxRun )
if filterSync . Opt . MinSize == - 1 {
// prevent overwriting Google Doc files (their size is -1)
filterSync . Opt . MinSize = 0
}
2023-11-06 15:34:47 +00:00
ci := fs . GetConfig ( ctxSync )
ci . IgnoreExisting = true
if results2to1 , err = b . resyncDir ( ctxSync , b . fs2 , b . fs1 ) ; err != nil {
2021-05-16 16:39:33 +00:00
b . critical = true
return err
}
2023-11-06 15:34:47 +00:00
b . indent ( "Path1" , "Path2" , "Resync is copying UNIQUE OR DIFFERING files to" )
ci . IgnoreExisting = false
if results1to2 , err = b . resyncDir ( ctxSync , b . fs1 , b . fs2 ) ; err != nil {
b . critical = true
return err
2023-07-11 11:09:06 +00:00
}
2021-05-16 16:39:33 +00:00
fs . Infof ( nil , "Resync updating listings" )
2023-11-09 10:04:33 +00:00
b . saveOldListings ( ) // may not exist, as this is --resync
2023-10-07 10:33:43 +00:00
b . replaceCurrentListings ( )
2023-10-01 13:36:19 +00:00
2023-11-06 15:34:47 +00:00
resultsToQueue := func ( results [ ] Results ) bilib . Names {
names := bilib . Names { }
for _ , result := range results {
if result . Name != "" &&
( result . Flags != "d" || b . opt . CreateEmptySrcDirs ) &&
result . IsSrc && result . Src != "" &&
( result . Winner . Err == nil || result . Flags == "d" ) {
names . Add ( result . Name )
}
}
return names
}
2023-10-01 13:36:19 +00:00
// resync 2to1
2023-11-06 15:34:47 +00:00
queues . copy2to1 = resultsToQueue ( results2to1 )
2023-10-06 20:38:47 +00:00
if err = b . modifyListing ( fctx , b . fs2 , b . fs1 , results2to1 , queues , false ) ; err != nil {
2023-10-01 13:36:19 +00:00
b . critical = true
return err
}
// resync 1to2
2023-11-06 15:34:47 +00:00
queues . copy1to2 = resultsToQueue ( results1to2 )
2023-10-06 20:38:47 +00:00
if err = b . modifyListing ( fctx , b . fs1 , b . fs2 , results1to2 , queues , true ) ; err != nil {
2021-05-16 16:39:33 +00:00
b . critical = true
return err
}
if ! b . opt . NoCleanup {
2023-10-06 20:38:47 +00:00
_ = os . Remove ( b . newListing1 )
_ = os . Remove ( b . newListing2 )
2021-05-16 16:39:33 +00:00
}
return nil
}
// checkSync validates listings
func ( b * bisyncRun ) checkSync ( listing1 , listing2 string ) error {
files1 , err := b . loadListing ( listing1 )
if err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "cannot read prior listing of Path1: %w" , err )
2021-05-16 16:39:33 +00:00
}
files2 , err := b . loadListing ( listing2 )
if err != nil {
2021-11-04 10:12:57 +00:00
return fmt . Errorf ( "cannot read prior listing of Path2: %w" , err )
2021-05-16 16:39:33 +00:00
}
ok := true
for _ , file := range files1 . list {
2023-11-09 10:04:33 +00:00
if ! files2 . has ( file ) && ! files2 . has ( b . aliases . Alias ( file ) ) {
2021-05-16 16:39:33 +00:00
b . indent ( "ERROR" , file , "Path1 file not found in Path2" )
ok = false
}
}
for _ , file := range files2 . list {
2023-11-09 10:04:33 +00:00
if ! files1 . has ( file ) && ! files1 . has ( b . aliases . Alias ( file ) ) {
2021-05-16 16:39:33 +00:00
b . indent ( "ERROR" , file , "Path2 file not found in Path1" )
ok = false
}
}
if ! ok {
return errors . New ( "path1 and path2 are out of sync, run --resync to recover" )
}
return nil
}
// checkAccess validates access health
func ( b * bisyncRun ) checkAccess ( checkFiles1 , checkFiles2 bilib . Names ) error {
ok := true
opt := b . opt
prefix := "Access test failed:"
numChecks1 := len ( checkFiles1 )
numChecks2 := len ( checkFiles2 )
if numChecks1 == 0 || numChecks1 != numChecks2 {
2023-11-11 05:34:41 +00:00
if numChecks1 == 0 && numChecks2 == 0 {
fs . Logf ( "--check-access" , Color ( terminal . RedFg , "Failed to find any files named %s\n More info: %s" ) , Color ( terminal . CyanFg , opt . CheckFilename ) , Color ( terminal . BlueFg , "https://rclone.org/bisync/#check-access" ) )
}
2021-05-16 16:39:33 +00:00
fs . Errorf ( nil , "%s Path1 count %d, Path2 count %d - %s" , prefix , numChecks1 , numChecks2 , opt . CheckFilename )
ok = false
}
for file := range checkFiles1 {
if ! checkFiles2 . Has ( file ) {
b . indentf ( "ERROR" , file , "%s Path1 file not found in Path2" , prefix )
ok = false
}
}
for file := range checkFiles2 {
if ! checkFiles1 . Has ( file ) {
b . indentf ( "ERROR" , file , "%s Path2 file not found in Path1" , prefix )
ok = false
}
}
if ! ok {
return errors . New ( "check file check failed" )
}
fs . Infof ( nil , "Found %d matching %q files on both paths" , numChecks1 , opt . CheckFilename )
return nil
}
2023-10-06 20:38:47 +00:00
func ( b * bisyncRun ) testFn ( ) {
if b . opt . TestFn != nil {
b . opt . TestFn ( )
}
}
2023-10-07 10:33:43 +00:00
func ( b * bisyncRun ) handleErr ( o interface { } , msg string , err error , critical , retryable bool ) {
if err != nil {
if retryable {
b . retryable = true
}
if critical {
b . critical = true
fs . Errorf ( o , "%s: %v" , msg , err )
} else {
2023-11-09 10:04:33 +00:00
fs . Infof ( o , "%s: %v" , msg , err )
2023-10-07 10:33:43 +00:00
}
}
}