diff --git a/backend/combine/combine.go b/backend/combine/combine.go index 2457bc4f1..f83a3c486 100644 --- a/backend/combine/combine.go +++ b/backend/combine/combine.go @@ -289,6 +289,16 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (outFs fs } } + // Enable CleanUp when any upstreams support it + if features.CleanUp == nil { + for _, u := range f.upstreams { + if u.f.Features().CleanUp != nil { + features.CleanUp = f.CleanUp + break + } + } + } + // Enable ChangeNotify when any upstreams support it if features.ChangeNotify == nil { for _, u := range f.upstreams { @@ -890,6 +900,100 @@ func (f *Fs) Shutdown(ctx context.Context) error { }) } +// PublicLink generates a public link to the remote path (usually readable by anyone) +func (f *Fs) PublicLink(ctx context.Context, remote string, expire fs.Duration, unlink bool) (string, error) { + u, uRemote, err := f.findUpstream(remote) + if err != nil { + return "", err + } + do := u.f.Features().PublicLink + if do == nil { + return "", fs.ErrorNotImplemented + } + return do(ctx, uRemote, expire, unlink) +} + +// Put in to the remote path with the modTime given of the given size +// +// May create the object even if it returns an error - if so +// will return the object and the error, otherwise will return +// nil and the error +// +// May create duplicates or return errors if src already +// exists. +func (f *Fs) PutUnchecked(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) { + srcPath := src.Remote() + u, uRemote, err := f.findUpstream(srcPath) + if err != nil { + return nil, err + } + do := u.f.Features().PutUnchecked + if do == nil { + return nil, fs.ErrorNotImplemented + } + uSrc := fs.NewOverrideRemote(src, uRemote) + return do(ctx, in, uSrc, options...) +} + +// MergeDirs merges the contents of all the directories passed +// in into the first one and rmdirs the other directories. +func (f *Fs) MergeDirs(ctx context.Context, dirs []fs.Directory) error { + if len(dirs) == 0 { + return nil + } + var ( + u *upstream + uDirs []fs.Directory + ) + for _, dir := range dirs { + uNew, uDir, err := f.findUpstream(dir.Remote()) + if err != nil { + return err + } + if u == nil { + u = uNew + } else if u != uNew { + return fmt.Errorf("can't merge dirctories from different upstreams") + } + uDirs = append(uDirs, fs.NewOverrideDirectory(dir, uDir)) + } + do := u.f.Features().MergeDirs + if do == nil { + return fs.ErrorNotImplemented + } + return do(ctx, uDirs) +} + +// CleanUp the trash in the Fs +// +// Implement this if you have a way of emptying the trash or +// otherwise cleaning up old versions of files. +func (f *Fs) CleanUp(ctx context.Context) error { + return f.multithread(ctx, func(ctx context.Context, u *upstream) error { + if do := u.f.Features().CleanUp; do != nil { + return do(ctx) + } + return nil + }) +} + +// OpenWriterAt opens with a handle for random access writes +// +// Pass in the remote desired and the size if known. +// +// It truncates any existing object +func (f *Fs) OpenWriterAt(ctx context.Context, remote string, size int64) (fs.WriterAtCloser, error) { + u, uRemote, err := f.findUpstream(remote) + if err != nil { + return nil, err + } + do := u.f.Features().OpenWriterAt + if do == nil { + return nil, fs.ErrorNotImplemented + } + return do(ctx, uRemote, size) +} + // Object describes a wrapped Object // // This is a wrapped Object which knows its path prefix @@ -991,5 +1095,10 @@ var ( _ fs.Abouter = (*Fs)(nil) _ fs.ListRer = (*Fs)(nil) _ fs.Shutdowner = (*Fs)(nil) + _ fs.PublicLinker = (*Fs)(nil) + _ fs.PutUncheckeder = (*Fs)(nil) + _ fs.MergeDirser = (*Fs)(nil) + _ fs.CleanUpper = (*Fs)(nil) + _ fs.OpenWriterAter = (*Fs)(nil) _ fs.FullObject = (*Object)(nil) ) diff --git a/backend/combine/combine_test.go b/backend/combine/combine_test.go index 46b49bcf0..0a0d357dd 100644 --- a/backend/combine/combine_test.go +++ b/backend/combine/combine_test.go @@ -10,6 +10,11 @@ import ( "github.com/rclone/rclone/fstest/fstests" ) +var ( + unimplementableFsMethods = []string{"UnWrap", "WrapFs", "SetWrapper", "UserInfo", "Disconnect"} + unimplementableObjectMethods = []string{} +) + // TestIntegration runs integration tests against the remote func TestIntegration(t *testing.T) { if *fstest.RemoteName == "" { @@ -17,8 +22,8 @@ func TestIntegration(t *testing.T) { } fstests.Run(t, &fstests.Opt{ RemoteName: *fstest.RemoteName, - UnimplementableFsMethods: []string{"OpenWriterAt", "DuplicateFiles"}, - UnimplementableObjectMethods: []string{"MimeType"}, + UnimplementableFsMethods: unimplementableFsMethods, + UnimplementableObjectMethods: unimplementableObjectMethods, }) } @@ -35,7 +40,9 @@ func TestLocal(t *testing.T) { {Name: name, Key: "type", Value: "combine"}, {Name: name, Key: "upstreams", Value: upstreams}, }, - QuickTestOK: true, + QuickTestOK: true, + UnimplementableFsMethods: unimplementableFsMethods, + UnimplementableObjectMethods: unimplementableObjectMethods, }) } @@ -51,7 +58,9 @@ func TestMemory(t *testing.T) { {Name: name, Key: "type", Value: "combine"}, {Name: name, Key: "upstreams", Value: upstreams}, }, - QuickTestOK: true, + QuickTestOK: true, + UnimplementableFsMethods: unimplementableFsMethods, + UnimplementableObjectMethods: unimplementableObjectMethods, }) } @@ -68,6 +77,8 @@ func TestMixed(t *testing.T) { {Name: name, Key: "type", Value: "combine"}, {Name: name, Key: "upstreams", Value: upstreams}, }, + UnimplementableFsMethods: unimplementableFsMethods, + UnimplementableObjectMethods: unimplementableObjectMethods, }) }