From 4cebade95d60eccdc06cde37151b2045c09096a8 Mon Sep 17 00:00:00 2001 From: SwazRGB <65694696+swazrgb@users.noreply.github.com> Date: Tue, 15 Mar 2022 04:55:20 +0100 Subject: [PATCH] b2: Add b2-version-at flag to show file versions at time Uses b2_list_file_versions to retrieve all file versions, and returns the one that was active at the specified time This is especially useful in combination with other backup tools, such as restic, which may use rclone as a backend. --- backend/b2/b2.go | 34 ++++++++++++++++++++++++++++++++-- docs/content/b2.md | 5 +++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/backend/b2/b2.go b/backend/b2/b2.go index d347c3c8e..ba3e03eb7 100644 --- a/backend/b2/b2.go +++ b/backend/b2/b2.go @@ -64,7 +64,8 @@ const ( // Globals var ( - errNotWithVersions = errors.New("can't modify or delete files in --b2-versions mode") + errNotWithVersions = errors.New("can't modify or delete files in --b2-versions mode") + errNotWithVersionAt = errors.New("can't modify or delete files in --b2-version-at mode") ) // Register with Fs @@ -106,6 +107,11 @@ in the [b2 integrations checklist](https://www.backblaze.com/b2/docs/integration Help: "Include old versions in directory listings.\n\nNote that when using this no file write operations are permitted,\nso you can't upload files or delete them.", Default: false, Advanced: true, + }, { + Name: "version_at", + Help: "Show file versions as they were at the specified time.\n\nNote that when using this no file write operations are permitted,\nso you can't upload files or delete them.", + Default: fs.Time{}, + Advanced: true, }, { Name: "hard_delete", Help: "Permanently delete files on remote removal, otherwise hide files.", @@ -211,6 +217,7 @@ type Options struct { Endpoint string `config:"endpoint"` TestMode string `config:"test_mode"` Versions bool `config:"versions"` + VersionAt fs.Time `config:"version_at"` HardDelete bool `config:"hard_delete"` UploadCutoff fs.SizeSuffix `config:"upload_cutoff"` CopyCutoff fs.SizeSuffix `config:"copy_cutoff"` @@ -696,9 +703,12 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck Method: "POST", Path: "/b2_list_file_names", } - if hidden { + if hidden || f.opt.VersionAt.IsSet() { opts.Path = "/b2_list_file_versions" } + + lastFileName := "" + for { var response api.ListFileNamesResponse err := f.pacer.Call(func() (bool, error) { @@ -728,7 +738,21 @@ func (f *Fs) list(ctx context.Context, bucket, directory, prefix string, addBuck if addBucket { remote = path.Join(bucket, remote) } + + if f.opt.VersionAt.IsSet() { + if time.Time(file.UploadTimestamp).After(time.Time(f.opt.VersionAt)) { + // Ignore versions that were created after the specified time + continue + } + + if file.Name == lastFileName { + // Ignore versions before the already returned version + continue + } + } + // Send object + lastFileName = file.Name err = fn(remote, file, isDirectory) if err != nil { if err == errEndList { @@ -1828,6 +1852,9 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op if o.fs.opt.Versions { return errNotWithVersions } + if o.fs.opt.VersionAt.IsSet() { + return errNotWithVersionAt + } size := src.Size() bucket, bucketPath := o.split() @@ -1983,6 +2010,9 @@ func (o *Object) Remove(ctx context.Context) error { if o.fs.opt.Versions { return errNotWithVersions } + if o.fs.opt.VersionAt.IsSet() { + return errNotWithVersionAt + } if o.fs.opt.HardDelete { return o.fs.deleteByID(ctx, o.id, bucketPath) } diff --git a/docs/content/b2.md b/docs/content/b2.md index b2d44d2ad..04f3375b7 100644 --- a/docs/content/b2.md +++ b/docs/content/b2.md @@ -173,6 +173,11 @@ the file instead of hiding it. Old versions of files, where available, are visible using the `--b2-versions` flag. +It is also possible to view a bucket as it was at a certain point in time, +using the `--b2-version-at` flag. This will show the file versions as they +were at that time, showing files that have been deleted afterwards, and +hiding files that were created since. + If you wish to remove all the old versions then you can use the `rclone cleanup remote:bucket` command which will delete all the old versions of files, leaving the current ones intact. You can also