github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/bisync/bilib/canonical.go (about) 1 // Package bilib provides common stuff for bisync and bisync_test 2 package bilib 3 4 import ( 5 "context" 6 "os" 7 "path/filepath" 8 "regexp" 9 "runtime" 10 "strings" 11 12 "github.com/rclone/rclone/fs" 13 "github.com/rclone/rclone/fs/operations" 14 ) 15 16 // FsPath converts Fs to a suitable rclone argument 17 func FsPath(f fs.Info) string { 18 name, path, slash := f.Name(), f.Root(), "/" 19 if name == "local" { 20 slash = string(os.PathSeparator) 21 if runtime.GOOS == "windows" { 22 path = strings.ReplaceAll(path, "/", slash) 23 path = strings.TrimPrefix(path, `\\?\`) 24 } 25 } else { 26 path = name + ":" + path 27 } 28 if !strings.HasSuffix(path, slash) { 29 path += slash 30 } 31 return path 32 } 33 34 // CanonicalPath converts a remote to a suitable base file name 35 func CanonicalPath(remote string) string { 36 trimmed := strings.Trim(remote, `\/`) 37 return nonCanonicalChars.ReplaceAllString(trimmed, "_") 38 } 39 40 var nonCanonicalChars = regexp.MustCompile(`[\s\\/:?*]`) 41 42 // SessionName makes a unique base name for the sync operation 43 func SessionName(fs1, fs2 fs.Fs) string { 44 return StripHexString(CanonicalPath(FsPath(fs1))) + ".." + StripHexString(CanonicalPath(FsPath(fs2))) 45 } 46 47 // StripHexString strips the (first) canonical {hexstring} suffix 48 func StripHexString(path string) string { 49 open := strings.IndexRune(path, '{') 50 close := strings.IndexRune(path, '}') 51 if open >= 0 && close > open { 52 return path[:open] + path[close+1:] // (trailing underscore) 53 } 54 return path 55 } 56 57 // HasHexString returns true if path contains at least one canonical {hexstring} suffix 58 func HasHexString(path string) bool { 59 open := strings.IndexRune(path, '{') 60 if open >= 0 && strings.IndexRune(path, '}') > open { 61 return true 62 } 63 return false 64 } 65 66 // BasePath joins the workDir with the SessionName, stripping {hexstring} suffix if necessary 67 func BasePath(ctx context.Context, workDir string, fs1, fs2 fs.Fs) string { 68 suffixedSession := CanonicalPath(FsPath(fs1)) + ".." + CanonicalPath(FsPath(fs2)) 69 suffixedBasePath := filepath.Join(workDir, suffixedSession) 70 listing1 := suffixedBasePath + ".path1.lst" 71 listing2 := suffixedBasePath + ".path2.lst" 72 73 sessionName := SessionName(fs1, fs2) 74 basePath := filepath.Join(workDir, sessionName) 75 76 // Normalize to non-canonical version for overridden configs 77 // to ensure that backend-specific flags don't change the listing filename. 78 // For backward-compatibility, we first check if we found a listing file with the suffixed version. 79 // If so, we rename it (and overwrite non-suffixed version, if any.) 80 // If not, we carry on with the non-suffixed version. 81 // We should only find a suffixed version if bisync v1.66 or older created it. 82 if HasHexString(suffixedSession) && FileExists(listing1) { 83 fs.Infof(listing1, "renaming to: %s", basePath+".path1.lst") 84 if !operations.SkipDestructive(ctx, listing1, "rename to "+basePath+".path1.lst") { 85 _ = os.Rename(listing1, basePath+".path1.lst") 86 } 87 } 88 if HasHexString(suffixedSession) && FileExists(listing2) { 89 fs.Infof(listing2, "renaming to: %s", basePath+".path2.lst") 90 if !operations.SkipDestructive(ctx, listing1, "rename to "+basePath+".path2.lst") { 91 _ = os.Rename(listing2, basePath+".path2.lst") 92 } else { 93 return suffixedBasePath 94 } 95 } 96 return basePath 97 }