rclone/fs/dir_wrapper.go
nielash 252562d00a combine: fix CopyDirMetadata error on upstream root
Before this change, operations.CopyDirMetadata would fail with: `internal error:
expecting directory string from combine root '' to have SetMetadata method:
optional feature not implemented` if the dst was the root directory of a combine
upstream. This is because combine was returning a *fs.Dir, which does not
satisfy the fs.SetMetadataer interface.

While it is true that combine cannot set metadata on the root of an upstream
(see also #7652), this should not be considered an error that causes sync to do
high-level retries, abort without doing deletes, etc.

This change addresses the issue by creating a new type of DirWrapper that is
allowed to fail silently, for exceptional cases such as this where certain
special directories have more limited abilities than what the Fs usually
supports.

It is possible that other similar wrapping backends (Union?) may need this same
fix.
2024-03-07 11:09:07 +00:00

99 lines
2.6 KiB
Go

package fs
import (
"context"
"time"
)
// DirWrapper wraps a Directory object so the Remote can be overridden
type DirWrapper struct {
Directory // Directory we are wrapping
remote string // name of the directory
failSilently bool // if set, ErrorNotImplemented should not be considered an error for this directory
}
// NewDirWrapper creates a wrapper for a directory object
//
// This passes through optional methods and should be used for
// wrapping backends to wrap native directories.
func NewDirWrapper(remote string, d Directory) *DirWrapper {
return &DirWrapper{
Directory: d,
remote: remote,
}
}
// NewLimitedDirWrapper creates a DirWrapper that should fail silently instead of erroring for ErrorNotImplemented.
//
// Intended for exceptional dirs lacking abilities that the Fs otherwise usually supports
// (ex. a Combine root which can't set metadata/modtime, regardless of support by wrapped backend)
func NewLimitedDirWrapper(remote string, d Directory) *DirWrapper {
dw := NewDirWrapper(remote, d)
dw.failSilently = true
return dw
}
// String returns the name
func (d *DirWrapper) String() string {
return d.remote
}
// Remote returns the remote path
func (d *DirWrapper) Remote() string {
return d.remote
}
// SetRemote sets the remote
func (d *DirWrapper) SetRemote(remote string) *DirWrapper {
d.remote = remote
return d
}
// Metadata returns metadata for an DirEntry
//
// It should return nil if there is no Metadata
func (d *DirWrapper) Metadata(ctx context.Context) (Metadata, error) {
do, ok := d.Directory.(Metadataer)
if !ok {
return nil, nil
}
return do.Metadata(ctx)
}
// SetMetadata sets metadata for an DirEntry
//
// It should return fs.ErrorNotImplemented if it can't set metadata
func (d *DirWrapper) SetMetadata(ctx context.Context, metadata Metadata) error {
do, ok := d.Directory.(SetMetadataer)
if !ok {
if d.failSilently {
Debugf(d, "Can't SetMetadata for this directory (%T from %v) -- skipping", d.Directory, d.Fs())
return nil
}
return ErrorNotImplemented
}
return do.SetMetadata(ctx, metadata)
}
// SetModTime sets the metadata on the DirEntry to set the modification date
//
// If there is any other metadata it does not overwrite it.
func (d *DirWrapper) SetModTime(ctx context.Context, t time.Time) error {
do, ok := d.Directory.(SetModTimer)
if !ok {
if d.failSilently {
Debugf(d, "Can't SetModTime for this directory (%T from %v) -- skipping", d.Directory, d.Fs())
return nil
}
return ErrorNotImplemented
}
return do.SetModTime(ctx, t)
}
// Check interfaces
var (
_ DirEntry = (*DirWrapper)(nil)
_ Directory = (*DirWrapper)(nil)
_ FullDirectory = (*DirWrapper)(nil)
)