mirror of
https://github.com/rclone/rclone
synced 2024-12-03 13:14:37 +01:00
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:
parent
e9e9feb21e
commit
cc634213a5
@ -36,6 +36,7 @@ func TestIntegration(t *testing.T) {
|
|||||||
"GetTier",
|
"GetTier",
|
||||||
"SetTier",
|
"SetTier",
|
||||||
"Metadata",
|
"Metadata",
|
||||||
|
"SetMetadata",
|
||||||
},
|
},
|
||||||
UnimplementableFsMethods: []string{
|
UnimplementableFsMethods: []string{
|
||||||
"PublicLink",
|
"PublicLink",
|
||||||
|
@ -1119,6 +1119,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
|
|||||||
return do.Metadata(ctx)
|
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
|
// SetTier performs changing storage tier of the Object if
|
||||||
// multiple storage classes supported
|
// multiple storage classes supported
|
||||||
func (o *Object) SetTier(tier string) error {
|
func (o *Object) SetTier(tier string) error {
|
||||||
|
@ -1286,6 +1286,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
|
|||||||
return do.Metadata(ctx)
|
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
|
// Hash returns the selected checksum of the file
|
||||||
// If no checksum is available it returns ""
|
// If no checksum is available it returns ""
|
||||||
func (o *Object) Hash(ctx context.Context, ht hash.Type) (string, error) {
|
func (o *Object) Hash(ctx context.Context, ht hash.Type) (string, error) {
|
||||||
|
@ -1248,6 +1248,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
|
|||||||
return do.Metadata(ctx)
|
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
|
// MimeType returns the content type of the Object if
|
||||||
// known, or "" if not
|
// known, or "" if not
|
||||||
//
|
//
|
||||||
|
@ -535,6 +535,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
|
|||||||
return do.Metadata(ctx)
|
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
|
// Check the interfaces are satisfied
|
||||||
var (
|
var (
|
||||||
_ fs.Fs = (*Fs)(nil)
|
_ fs.Fs = (*Fs)(nil)
|
||||||
|
@ -322,6 +322,17 @@ func (o *Object) Metadata(ctx context.Context) (fs.Metadata, error) {
|
|||||||
return do.Metadata(ctx)
|
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
|
// Metadata returns metadata for an DirEntry
|
||||||
//
|
//
|
||||||
// It should return nil if there is no Metadata
|
// It should return nil if there is no Metadata
|
||||||
|
@ -242,6 +242,7 @@ type FullObject interface {
|
|||||||
GetTierer
|
GetTierer
|
||||||
SetTierer
|
SetTierer
|
||||||
Metadataer
|
Metadataer
|
||||||
|
SetMetadataer
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectOptionalInterfaces returns the names of supported and
|
// ObjectOptionalInterfaces returns the names of supported and
|
||||||
@ -273,6 +274,9 @@ func ObjectOptionalInterfaces(o Object) (supported, unsupported []string) {
|
|||||||
_, ok = o.(Metadataer)
|
_, ok = o.(Metadataer)
|
||||||
store(ok, "Metadata")
|
store(ok, "Metadata")
|
||||||
|
|
||||||
|
_, ok = o.(SetMetadataer)
|
||||||
|
store(ok, "SetMetadata")
|
||||||
|
|
||||||
return supported, unsupported
|
return supported, unsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!
|
} // 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
|
// TestObjectSetModTime tests that SetModTime works
|
||||||
t.Run("ObjectSetModTime", func(t *testing.T) {
|
t.Run("ObjectSetModTime", func(t *testing.T) {
|
||||||
skipIfNotOk(t)
|
skipIfNotOk(t)
|
||||||
|
Loading…
Reference in New Issue
Block a user