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