From 79f5d940cfbb5b392f57096d2118f94b64dfd0c9 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Fri, 19 Jun 2020 11:02:57 +0100 Subject: [PATCH] fs: add Fingerprint to detect changes in an object --- fs/fingerprint.go | 54 ++++++++++++++++++++++++++++++++++++++++++ fs/fingerprint_test.go | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 fs/fingerprint.go create mode 100644 fs/fingerprint_test.go diff --git a/fs/fingerprint.go b/fs/fingerprint.go new file mode 100644 index 000000000..75d3256db --- /dev/null +++ b/fs/fingerprint.go @@ -0,0 +1,54 @@ +package fs + +import ( + "context" + "fmt" + "strings" + + "github.com/rclone/rclone/fs/hash" +) + +// Fingerprint produces a unique-ish string for an object. +// +// This is for detecting whether an object has changed since we last +// saw it, not for checking object identity between two different +// remotes - operations.Equal should be used for that. +// +// If fast is set then Fingerprint will only include attributes where +// usually another operation is not required to fetch them. For +// example if fast is set then this won't include hashes on the local +// backend. +func Fingerprint(ctx context.Context, o ObjectInfo, fast bool) string { + var ( + out strings.Builder + f = o.Fs() + features = f.Features() + ) + fmt.Fprintf(&out, "%d", o.Size()) + // Whether we want to do a slow operation or not + // + // fast true false true false + // opIsSlow true true false false + // do Op false true true true + // + // If !fast (slow) do the operation or if !OpIsSlow == + // OpIsFast do the operation. + // + // Eg don't do this for S3 where modtimes are expensive + if !fast || !features.SlowModTime { + if f.Precision() != ModTimeNotSupported { + fmt.Fprintf(&out, ",%v", o.ModTime(ctx).UTC()) + } + } + // Eg don't do this for SFTP/local where hashes are expensive? + if !fast || !features.SlowHash { + hashType := f.Hashes().GetOne() + if hashType != hash.None { + hash, err := o.Hash(ctx, hashType) + if err == nil { + fmt.Fprintf(&out, ",%v", hash) + } + } + } + return out.String() +} diff --git a/fs/fingerprint_test.go b/fs/fingerprint_test.go new file mode 100644 index 000000000..cca35e97c --- /dev/null +++ b/fs/fingerprint_test.go @@ -0,0 +1,43 @@ +package fs_test + +import ( + "context" + "fmt" + "testing" + + "github.com/rclone/rclone/fs" + "github.com/rclone/rclone/fs/hash" + "github.com/rclone/rclone/fstest/mockfs" + "github.com/rclone/rclone/fstest/mockobject" + "github.com/stretchr/testify/assert" +) + +func TestFingerprint(t *testing.T) { + ctx := context.Background() + f := mockfs.NewFs("test", "root") + f.SetHashes(hash.NewHashSet(hash.MD5)) + + for i, test := range []struct { + fast bool + slowModTime bool + slowHash bool + want string + }{ + {fast: false, slowModTime: false, slowHash: false, want: "4,0001-01-01 00:00:00 +0000 UTC,8d777f385d3dfec8815d20f7496026dc"}, + {fast: false, slowModTime: false, slowHash: true, want: "4,0001-01-01 00:00:00 +0000 UTC,8d777f385d3dfec8815d20f7496026dc"}, + {fast: false, slowModTime: true, slowHash: false, want: "4,0001-01-01 00:00:00 +0000 UTC,8d777f385d3dfec8815d20f7496026dc"}, + {fast: false, slowModTime: true, slowHash: true, want: "4,0001-01-01 00:00:00 +0000 UTC,8d777f385d3dfec8815d20f7496026dc"}, + {fast: true, slowModTime: false, slowHash: false, want: "4,0001-01-01 00:00:00 +0000 UTC,8d777f385d3dfec8815d20f7496026dc"}, + {fast: true, slowModTime: false, slowHash: true, want: "4,0001-01-01 00:00:00 +0000 UTC"}, + {fast: true, slowModTime: true, slowHash: false, want: "4,8d777f385d3dfec8815d20f7496026dc"}, + {fast: true, slowModTime: true, slowHash: true, want: "4"}, + } { + what := fmt.Sprintf("#%d fast=%v,slowModTime=%v,slowHash=%v", i, test.fast, test.slowModTime, test.slowHash) + o := mockobject.New("potato").WithContent([]byte("data"), mockobject.SeekModeRegular) + o.SetFs(f) + f.Features().SlowModTime = test.slowModTime + f.Features().SlowHash = test.slowHash + got := fs.Fingerprint(ctx, o, test.fast) + assert.Equal(t, test.want, got, what) + } +}