From 280ac26464417033224b97f74f6f92f932ddcb1d Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Mon, 29 Feb 2016 17:46:40 +0000 Subject: [PATCH] Implement -u/--update so creation times can be used on all remotes - #226 --- docs/content/docs.md | 19 +++++++++++++++++++ fs/config.go | 3 +++ fs/operations.go | 36 ++++++++++++++++++++++++++++++++---- fs/operations_test.go | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 4 deletions(-) diff --git a/docs/content/docs.md b/docs/content/docs.md index 002a029a5..e2602d19a 100644 --- a/docs/content/docs.md +++ b/docs/content/docs.md @@ -457,6 +457,25 @@ of timeouts or bigger if you have lots of bandwidth and a fast remote. The default is to run 4 file transfers in parallel. +### -u, --update ### + +This forces rclone to skip any files which exist on the destination +and have a modified time that is newer than the source file. + +If an existing destination file has a modification time equal (within +the computed modify window precision) to the source file's, it will be +updated if the sizes are different. + +On remotes which don't support mod time directly the time checked will +be the uploaded time. This means that if uploading to one of these +remoes, rclone will skip any files which exist on the destination and +have an uploaded time that is newer than the modification time of the +source file. + +This can be useful when transferring to a remote which doesn't support +mod times directly as it is more accurate than a `--size-only` check +and faster than using `--checksum`. + ### -v, --verbose ### If you set this flag, rclone will become very verbose telling you diff --git a/fs/config.go b/fs/config.go index 9ad624429..80134cfdf 100644 --- a/fs/config.go +++ b/fs/config.go @@ -81,6 +81,7 @@ var ( deleteDuring = pflag.BoolP("delete-during", "", false, "When synchronizing, delete files during transfer (default)") deleteAfter = pflag.BoolP("delete-after", "", false, "When synchronizing, delete files on destination after transfering") lowLevelRetries = pflag.IntP("low-level-retries", "", 10, "Number of low level retries to do.") + updateOlder = pflag.BoolP("update", "u", false, "Skip files that are newer on the destination.") bwLimit SizeSuffix // Key to use for password en/decryption. @@ -199,6 +200,7 @@ type ConfigInfo struct { DeleteDuring bool // Delete during checking/transfer DeleteAfter bool // Delete after successful transfer. LowLevelRetries int + UpdateOlder bool // Skip files that are newer on the destination } // Transport returns an http.RoundTripper with the correct timeouts @@ -288,6 +290,7 @@ func LoadConfig() { Config.DumpBodies = *dumpBodies Config.InsecureSkipVerify = *skipVerify Config.LowLevelRetries = *lowLevelRetries + Config.UpdateOlder = *updateOlder ConfigPath = *configFile diff --git a/fs/operations.go b/fs/operations.go index f0dbd5657..e042fe106 100644 --- a/fs/operations.go +++ b/fs/operations.go @@ -306,10 +306,38 @@ func checkOne(pair ObjectPair, out ObjectPairChan) { Debug(src, "Destination exists, skipping") return } - // Check to see if changed or not - if Equal(src, dst) { - Debug(src, "Unchanged skipping") - return + // If UpdateOlder is in effect, skip if dst is newer than src + if Config.UpdateOlder { + srcModTime := src.ModTime() + dstModTime := dst.ModTime() + dt := dstModTime.Sub(srcModTime) + // If have a mutually agreed precision then use that + modifyWindow := Config.ModifyWindow + if modifyWindow == ModTimeNotSupported { + // Otherwise use 1 second as a safe default as + // the resolution of the time a file was + // uploaded. + modifyWindow = time.Second + } + switch { + case dt >= modifyWindow: + Debug(src, "Destination is newer than source, skipping") + return + case dt <= -modifyWindow: + Debug(src, "Destination is older than source, transferring") + default: + if src.Size() == dst.Size() { + Debug(src, "Destination mod time is within %v of source and sizes identical, skipping", modifyWindow) + return + } + Debug(src, "Destination mod time is within %v of source but sizes differ, transferring", modifyWindow) + } + } else { + // Check to see if changed or not + if Equal(src, dst) { + Debug(src, "Unchanged skipping") + return + } } out <- pair } diff --git a/fs/operations_test.go b/fs/operations_test.go index 1bc91597e..be102925d 100644 --- a/fs/operations_test.go +++ b/fs/operations_test.go @@ -692,6 +692,40 @@ func TestSyncWithExcludeAndDeleteExcluded(t *testing.T) { fstest.CheckItems(t, r.flocal, file2) } +// Test with UpdateOlder set +func TestSyncWithUpdateOlder(t *testing.T) { + r := NewRun(t) + defer r.Finalise() + t2plus := t2.Add(time.Second / 2) + t2minus := t2.Add(time.Second / 2) + oneF := r.WriteFile("one", "one", t1) + twoF := r.WriteFile("two", "two", t3) + threeF := r.WriteFile("three", "three", t2) + fourF := r.WriteFile("four", "four", t2) + fiveF := r.WriteFile("five", "five", t2) + fstest.CheckItems(t, r.flocal, oneF, twoF, threeF, fourF, fiveF) + oneO := r.WriteObject("one", "ONE", t2) + twoO := r.WriteObject("two", "TWO", t2) + threeO := r.WriteObject("three", "THREE", t2plus) + fourO := r.WriteObject("four", "FOURFOUR", t2minus) + fstest.CheckItems(t, r.fremote, oneO, twoO, threeO, fourO) + + fs.Config.UpdateOlder = true + oldModifyWindow := fs.Config.ModifyWindow + fs.Config.ModifyWindow = fs.ModTimeNotSupported + defer func() { + fs.Config.UpdateOlder = false + fs.Config.ModifyWindow = oldModifyWindow + }() + + fs.Stats.ResetCounters() + err := fs.Sync(r.fremote, r.flocal) + if err != nil { + t.Fatalf("Sync failed: %v", err) + } + fstest.CheckItems(t, r.fremote, oneO, twoF, threeO, fourF, fiveF) +} + // Test a server side move if possible, or the backup path if not func TestServerSideMove(t *testing.T) { r := NewRun(t)