fs: define the optional interface SetMetadata and implement it in wrapping backends

This also implements backend integration tests for the feature
This commit is contained in:
Nick Craig-Wood 2024-05-08 17:06:55 +01:00
parent e9e9feb21e
commit cc634213a5
8 changed files with 121 additions and 0 deletions

View File

@ -36,6 +36,7 @@ func TestIntegration(t *testing.T) {
"GetTier",
"SetTier",
"Metadata",
"SetMetadata",
},
UnimplementableFsMethods: []string{
"PublicLink",

View File

@ -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 {

View File

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

View File

@ -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
//

View File

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

View File

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

View File

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

View File

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