diff --git a/backend/chunker/chunker_test.go b/backend/chunker/chunker_test.go index d5ce656dd..9f6efbd33 100644 --- a/backend/chunker/chunker_test.go +++ b/backend/chunker/chunker_test.go @@ -36,6 +36,7 @@ func TestIntegration(t *testing.T) { "GetTier", "SetTier", "Metadata", + "SetMetadata", }, UnimplementableFsMethods: []string{ "PublicLink", diff --git a/backend/combine/combine.go b/backend/combine/combine.go index c00f2314e..d1036ae3a 100644 --- a/backend/combine/combine.go +++ b/backend/combine/combine.go @@ -1119,6 +1119,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) { return do.Metadata(ctx) } +// SetMetadata sets metadata for an Object +// +// It should return fs.ErrorNotImplemented if it can't set metadata +func (o *Object) SetMetadata(ctx context.Context, metadata fs.Metadata) error { + do, ok := o.Object.(fs.SetMetadataer) + if !ok { + return fs.ErrorNotImplemented + } + return do.SetMetadata(ctx, metadata) +} + // SetTier performs changing storage tier of the Object if // multiple storage classes supported func (o *Object) SetTier(tier string) error { diff --git a/backend/compress/compress.go b/backend/compress/compress.go index c11906a52..038391602 100644 --- a/backend/compress/compress.go +++ b/backend/compress/compress.go @@ -1286,6 +1286,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) { return do.Metadata(ctx) } +// SetMetadata sets metadata for an Object +// +// It should return fs.ErrorNotImplemented if it can't set metadata +func (o *Object) SetMetadata(ctx context.Context, metadata fs.Metadata) error { + do, ok := o.Object.(fs.SetMetadataer) + if !ok { + return fs.ErrorNotImplemented + } + return do.SetMetadata(ctx, metadata) +} + // Hash returns the selected checksum of the file // If no checksum is available it returns "" func (o *Object) Hash(ctx context.Context, ht hash.Type) (string, error) { diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go index c7511ff7a..2303c0851 100644 --- a/backend/crypt/crypt.go +++ b/backend/crypt/crypt.go @@ -1248,6 +1248,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) { return do.Metadata(ctx) } +// SetMetadata sets metadata for an Object +// +// It should return fs.ErrorNotImplemented if it can't set metadata +func (o *Object) SetMetadata(ctx context.Context, metadata fs.Metadata) error { + do, ok := o.Object.(fs.SetMetadataer) + if !ok { + return fs.ErrorNotImplemented + } + return do.SetMetadata(ctx, metadata) +} + // MimeType returns the content type of the Object if // known, or "" if not // diff --git a/backend/hasher/hasher.go b/backend/hasher/hasher.go index c50f2404a..3cd75a5c7 100644 --- a/backend/hasher/hasher.go +++ b/backend/hasher/hasher.go @@ -535,6 +535,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) { return do.Metadata(ctx) } +// SetMetadata sets metadata for an Object +// +// It should return fs.ErrorNotImplemented if it can't set metadata +func (o *Object) SetMetadata(ctx context.Context, metadata fs.Metadata) error { + do, ok := o.Object.(fs.SetMetadataer) + if !ok { + return fs.ErrorNotImplemented + } + return do.SetMetadata(ctx, metadata) +} + // Check the interfaces are satisfied var ( _ fs.Fs = (*Fs)(nil) diff --git a/backend/union/upstream/upstream.go b/backend/union/upstream/upstream.go index b86fa9797..747de830d 100644 --- a/backend/union/upstream/upstream.go +++ b/backend/union/upstream/upstream.go @@ -322,6 +322,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) { return do.Metadata(ctx) } +// SetMetadata sets metadata for an Object +// +// It should return fs.ErrorNotImplemented if it can't set metadata +func (o *Object) SetMetadata(ctx context.Context, metadata fs.Metadata) error { + do, ok := o.Object.(fs.SetMetadataer) + if !ok { + return fs.ErrorNotImplemented + } + return do.SetMetadata(ctx, metadata) +} + // Metadata returns metadata for an DirEntry // // It should return nil if there is no Metadata diff --git a/fs/types.go b/fs/types.go index a709f628b..18c9848e5 100644 --- a/fs/types.go +++ b/fs/types.go @@ -242,6 +242,7 @@ type FullObject interface { GetTierer SetTierer Metadataer + SetMetadataer } // ObjectOptionalInterfaces returns the names of supported and @@ -273,6 +274,9 @@ func ObjectOptionalInterfaces(o Object) (supported, unsupported []string) { _, ok = o.(Metadataer) store(ok, "Metadata") + _, ok = o.(SetMetadataer) + store(ok, "SetMetadata") + return supported, unsupported } diff --git a/fstest/fstests/fstests.go b/fstest/fstests/fstests.go index 80be1cbe0..debe5b0f8 100644 --- a/fstest/fstests/fstests.go +++ b/fstest/fstests/fstests.go @@ -1668,6 +1668,67 @@ func Run(t *testing.T, opt *Opt) { } // else: Have some metadata here we didn't write - can't really check it! }) + // TestObjectSetMetadata tests the SetMetadata of the object + t.Run("ObjectSetMetadata", func(t *testing.T) { + skipIfNotOk(t) + ctx, ci := fs.AddConfig(ctx) + ci.Metadata = true + features := f.Features() + + // Test to see if SetMetadata is supported on an existing object before creating a new one + obj := fstest.NewObject(ctx, t, f, file1.Path) + _, objectHasSetMetadata := obj.(fs.SetMetadataer) + if !objectHasSetMetadata { + t.Skip("SetMetadata method not supported") + } + if !features.Overlay { + require.True(t, features.WriteMetadata, "Features.WriteMetadata is false but Object.SetMetadata found") + } + if !features.ReadMetadata { + t.Skip("SetMetadata can't be tested without ReadMetadata") + } + + // Create file with metadata + const fileName = "test set metadata.txt" + t1 := fstest.Time("2003-02-03T04:05:06.499999999Z") + t2 := fstest.Time("2004-03-03T04:05:06.499999999Z") + contents := random.String(100) + file := fstest.NewItem(fileName, contents, t1) + var testMetadata = fs.Metadata{ + // System metadata supported by all backends + "mtime": t1.Format(time.RFC3339Nano), + // User metadata + "potato": "jersey", + } + obj = PutTestContentsMetadata(ctx, t, f, &file, contents, true, "text/plain", testMetadata) + fstest.CheckEntryMetadata(ctx, t, f, obj, testMetadata) + do, objectHasSetMetadata := obj.(fs.SetMetadataer) + require.True(t, objectHasSetMetadata) + + // Set new metadata + err := do.SetMetadata(ctx, fs.Metadata{ + // System metadata supported by all backends + "mtime": t2.Format(time.RFC3339Nano), + // User metadata + "potato": "royal", + }) + if err == fs.ErrorNotImplemented { + t.Log("SetMetadata returned fs.ErrorNotImplemented") + } else { + require.NoError(t, err) + file.ModTime = t2 + fstest.CheckListing(t, f, []fstest.Item{file1, file2, file}) + + // Check metadata is correct + fstest.CheckEntryMetadata(ctx, t, f, obj, ci.MetadataSet) + obj = fstest.NewObject(ctx, t, f, fileName) + fstest.CheckEntryMetadata(ctx, t, f, obj, ci.MetadataSet) + } + + // Remove test file + require.NoError(t, obj.Remove(ctx)) + }) + // TestObjectSetModTime tests that SetModTime works t.Run("ObjectSetModTime", func(t *testing.T) { skipIfNotOk(t)