1
mirror of https://github.com/rclone/rclone synced 2024-11-20 21:27:33 +01:00

fs: add Fingerprint to detect changes in an object

This commit is contained in:
Nick Craig-Wood 2020-06-19 11:02:57 +01:00
parent 25662b9e05
commit 79f5d940cf
2 changed files with 97 additions and 0 deletions

54
fs/fingerprint.go Normal file
View File

@ -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()
}

43
fs/fingerprint_test.go Normal file
View File

@ -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)
}
}