From c163e6b2507e309576461fbd2b2d5fce3f202877 Mon Sep 17 00:00:00 2001 From: Dominik Mydlil Date: Tue, 6 Apr 2021 21:45:17 +0200 Subject: [PATCH] b2: factor version handling into lib/version Standardizes the filename version tagging so that it can be used by any backend. --- backend/b2/api/types.go | 39 ++++++------------- backend/b2/api/types_test.go | 35 ----------------- lib/version/version.go | 52 +++++++++++++++++++++++++ lib/version/version_test.go | 73 ++++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 62 deletions(-) create mode 100644 lib/version/version.go create mode 100644 lib/version/version_test.go diff --git a/backend/b2/api/types.go b/backend/b2/api/types.go index a9d6af2b1..e139dc8c1 100644 --- a/backend/b2/api/types.go +++ b/backend/b2/api/types.go @@ -2,12 +2,11 @@ package api import ( "fmt" - "path" "strconv" - "strings" "time" "github.com/rclone/rclone/fs/fserrors" + "github.com/rclone/rclone/lib/version" ) // Error describes a B2 error response @@ -63,16 +62,17 @@ func (t *Timestamp) UnmarshalJSON(data []byte) error { return nil } -const versionFormat = "-v2006-01-02-150405.000" +// HasVersion returns true if it looks like the passed filename has a timestamp on it. +// +// Note that the passed filename's timestamp may still be invalid even if this +// function returns true. +func HasVersion(remote string) bool { + return version.Match(remote) +} // AddVersion adds the timestamp as a version string into the filename passed in. func (t Timestamp) AddVersion(remote string) string { - ext := path.Ext(remote) - base := remote[:len(remote)-len(ext)] - s := time.Time(t).Format(versionFormat) - // Replace the '.' with a '-' - s = strings.Replace(s, ".", "-", -1) - return base + s + ext + return version.Add(remote, time.Time(t)) } // RemoveVersion removes the timestamp from a filename as a version string. @@ -80,24 +80,9 @@ func (t Timestamp) AddVersion(remote string) string { // It returns the new file name and a timestamp, or the old filename // and a zero timestamp. func RemoveVersion(remote string) (t Timestamp, newRemote string) { - newRemote = remote - ext := path.Ext(remote) - base := remote[:len(remote)-len(ext)] - if len(base) < len(versionFormat) { - return - } - versionStart := len(base) - len(versionFormat) - // Check it ends in -xxx - if base[len(base)-4] != '-' { - return - } - // Replace with .xxx for parsing - base = base[:len(base)-4] + "." + base[len(base)-3:] - newT, err := time.Parse(versionFormat, base[versionStart:]) - if err != nil { - return - } - return Timestamp(newT), base[:versionStart] + ext + time, newRemote := version.Remove(remote) + t = Timestamp(time) + return } // IsZero returns true if the timestamp is uninitialized diff --git a/backend/b2/api/types_test.go b/backend/b2/api/types_test.go index c56c3d9cc..6074de017 100644 --- a/backend/b2/api/types_test.go +++ b/backend/b2/api/types_test.go @@ -13,7 +13,6 @@ import ( var ( emptyT api.Timestamp t0 = api.Timestamp(fstest.Time("1970-01-01T01:01:01.123456789Z")) - t0r = api.Timestamp(fstest.Time("1970-01-01T01:01:01.123000000Z")) t1 = api.Timestamp(fstest.Time("2001-02-03T04:05:06.123000000Z")) ) @@ -36,40 +35,6 @@ func TestTimestampUnmarshalJSON(t *testing.T) { assert.Equal(t, (time.Time)(t1), (time.Time)(tActual)) } -func TestTimestampAddVersion(t *testing.T) { - for _, test := range []struct { - t api.Timestamp - in string - expected string - }{ - {t0, "potato.txt", "potato-v1970-01-01-010101-123.txt"}, - {t1, "potato", "potato-v2001-02-03-040506-123"}, - {t1, "", "-v2001-02-03-040506-123"}, - } { - actual := test.t.AddVersion(test.in) - assert.Equal(t, test.expected, actual, test.in) - } -} - -func TestTimestampRemoveVersion(t *testing.T) { - for _, test := range []struct { - in string - expectedT api.Timestamp - expectedRemote string - }{ - {"potato.txt", emptyT, "potato.txt"}, - {"potato-v1970-01-01-010101-123.txt", t0r, "potato.txt"}, - {"potato-v2001-02-03-040506-123", t1, "potato"}, - {"-v2001-02-03-040506-123", t1, ""}, - {"potato-v2A01-02-03-040506-123", emptyT, "potato-v2A01-02-03-040506-123"}, - {"potato-v2001-02-03-040506=123", emptyT, "potato-v2001-02-03-040506=123"}, - } { - actualT, actualRemote := api.RemoveVersion(test.in) - assert.Equal(t, test.expectedT, actualT, test.in) - assert.Equal(t, test.expectedRemote, actualRemote, test.in) - } -} - func TestTimestampIsZero(t *testing.T) { assert.True(t, emptyT.IsZero()) assert.False(t, t0.IsZero()) diff --git a/lib/version/version.go b/lib/version/version.go new file mode 100644 index 000000000..2b13f6b44 --- /dev/null +++ b/lib/version/version.go @@ -0,0 +1,52 @@ +// Package version provides machinery for versioning file names +// with a timestamp-based version string +package version + +import ( + "path" + "regexp" + "strings" + "time" +) + +const versionFormat = "-v2006-01-02-150405.000" + +var versionRegexp = regexp.MustCompile("-v\\d{4}-\\d{2}-\\d{2}-\\d{6}-\\d{3}") + +// Add returns fileName modified to include t as the version +func Add(fileName string, t time.Time) string { + ext := path.Ext(fileName) + base := fileName[:len(fileName)-len(ext)] + s := t.Format(versionFormat) + // Replace the '.' with a '-' + s = strings.Replace(s, ".", "-", -1) + return base + s + ext +} + +// Remove returns a modified fileName without the version string and the time it represented +// If the fileName did not have a version then time.Time{} is returned along with an unmodified fileName +func Remove(fileName string) (t time.Time, fileNameWithoutVersion string) { + fileNameWithoutVersion = fileName + ext := path.Ext(fileName) + base := fileName[:len(fileName)-len(ext)] + if len(base) < len(versionFormat) { + return + } + versionStart := len(base) - len(versionFormat) + // Check it ends in -xxx + if base[len(base)-4] != '-' { + return + } + // Replace with .xxx for parsing + base = base[:len(base)-4] + "." + base[len(base)-3:] + newT, err := time.Parse(versionFormat, base[versionStart:]) + if err != nil { + return + } + return newT, base[:versionStart] + ext +} + +// Match returns true if the fileName has a version string +func Match(fileName string) bool { + return versionRegexp.MatchString(fileName) +} diff --git a/lib/version/version_test.go b/lib/version/version_test.go new file mode 100644 index 000000000..7517fb329 --- /dev/null +++ b/lib/version/version_test.go @@ -0,0 +1,73 @@ +package version_test + +import ( + "testing" + "time" + + "github.com/rclone/rclone/fstest" + "github.com/rclone/rclone/lib/version" + "github.com/stretchr/testify/assert" +) + +var ( + emptyT time.Time + t0 = fstest.Time("1970-01-01T01:01:01.123456789Z") + t0r = fstest.Time("1970-01-01T01:01:01.123000000Z") + t1 = fstest.Time("2001-02-03T04:05:06.123000000Z") +) + +func TestVersionAdd(t *testing.T) { + for _, test := range []struct { + t time.Time + in string + expected string + }{ + {t0, "potato.txt", "potato-v1970-01-01-010101-123.txt"}, + {t0, "potato-v2001-02-03-040506-123.txt", "potato-v2001-02-03-040506-123-v1970-01-01-010101-123.txt"}, + {t0, "123.!!lipps", "123-v1970-01-01-010101-123.!!lipps"}, + {t1, "potato", "potato-v2001-02-03-040506-123"}, + {t1, "", "-v2001-02-03-040506-123"}, + } { + actual := version.Add(test.in, test.t) + assert.Equal(t, test.expected, actual, test.in) + } +} + +func TestVersionRemove(t *testing.T) { + for _, test := range []struct { + in string + expectedT time.Time + expectedRemote string + }{ + {"potato.txt", emptyT, "potato.txt"}, + {"potato-v1970-01-01-010101-123.txt", t0r, "potato.txt"}, + {"potato-v2001-02-03-040506-123-v1970-01-01-010101-123.txt", t0r, "potato-v2001-02-03-040506-123.txt"}, + {"potato-v2001-02-03-040506-123", t1, "potato"}, + {"-v2001-02-03-040506-123", t1, ""}, + {"potato-v2A01-02-03-040506-123", emptyT, "potato-v2A01-02-03-040506-123"}, + {"potato-v2001-02-03-040506=123", emptyT, "potato-v2001-02-03-040506=123"}, + } { + actualT, actualRemote := version.Remove(test.in) + assert.Equal(t, test.expectedT, actualT, test.in) + assert.Equal(t, test.expectedRemote, actualRemote, test.in) + } +} + +func TestVersionMatch(t *testing.T) { + for _, test := range []struct { + in string + expected bool + }{ + {"potato.txt", false}, + {"potato", false}, + {"", false}, + {"potato-v1970-01-01-010101-123.txt", true}, + {"potato-v2001-02-03-040506-123-v1970-01-01-010101-123.txt", true}, + {"potato-v2001-02-03-040506-123", true}, + {"-v2001-02-03-040506-123", true}, + {"-v9999-99-99-999999-999", true}, + } { + actual := version.Match(test.in) + assert.Equal(t, test.expected, actual, test.in) + } +}