diff --git a/backend/cache/cache.go b/backend/cache/cache.go index cc9c5269a..29b373287 100644 --- a/backend/cache/cache.go +++ b/backend/cache/cache.go @@ -1895,6 +1895,16 @@ func (f *Fs) Disconnect(ctx context.Context) error { return do(ctx) } +// Shutdown the backend, closing any background tasks and any +// cached connections. +func (f *Fs) Shutdown(ctx context.Context) error { + do := f.Fs.Features().Shutdown + if do == nil { + return nil + } + return do(ctx) +} + var commandHelp = []fs.CommandHelp{ { Name: "stats", @@ -1939,4 +1949,5 @@ var ( _ fs.Disconnecter = (*Fs)(nil) _ fs.Commander = (*Fs)(nil) _ fs.MergeDirser = (*Fs)(nil) + _ fs.Shutdowner = (*Fs)(nil) ) diff --git a/backend/chunker/chunker.go b/backend/chunker/chunker.go index 9e8868980..b96e4caab 100644 --- a/backend/chunker/chunker.go +++ b/backend/chunker/chunker.go @@ -1724,6 +1724,16 @@ func (f *Fs) ChangeNotify(ctx context.Context, notifyFunc func(string, fs.EntryT do(ctx, wrappedNotifyFunc, pollIntervalChan) } +// Shutdown the backend, closing any background tasks and any +// cached connections. +func (f *Fs) Shutdown(ctx context.Context) error { + do := f.base.Features().Shutdown + if do == nil { + return nil + } + return do(ctx) +} + // Object represents a composite file wrapping one or more data chunks type Object struct { remote string @@ -2298,6 +2308,7 @@ var ( _ fs.Abouter = (*Fs)(nil) _ fs.Wrapper = (*Fs)(nil) _ fs.ChangeNotifier = (*Fs)(nil) + _ fs.Shutdowner = (*Fs)(nil) _ fs.ObjectInfo = (*ObjectInfo)(nil) _ fs.Object = (*Object)(nil) _ fs.ObjectUnWrapper = (*Object)(nil) diff --git a/backend/compress/compress.go b/backend/compress/compress.go index 6be2af272..54b37129c 100644 --- a/backend/compress/compress.go +++ b/backend/compress/compress.go @@ -1074,6 +1074,16 @@ func (f *Fs) newObjectSizeAndNameOnly(o fs.Object, moName string, size int64) *O } } +// Shutdown the backend, closing any background tasks and any +// cached connections. +func (f *Fs) Shutdown(ctx context.Context) error { + do := f.Fs.Features().Shutdown + if do == nil { + return nil + } + return do(ctx) +} + // This loads the metadata of a press Object if it's not loaded yet func (o *Object) loadMetadataIfNotLoaded(ctx context.Context) (err error) { err = o.loadMetadataObjectIfNotLoaded(ctx) @@ -1337,6 +1347,7 @@ var ( _ fs.DirCacheFlusher = (*Fs)(nil) _ fs.ChangeNotifier = (*Fs)(nil) _ fs.PublicLinker = (*Fs)(nil) + _ fs.Shutdowner = (*Fs)(nil) _ fs.ObjectInfo = (*ObjectInfo)(nil) _ fs.GetTierer = (*Object)(nil) _ fs.SetTierer = (*Object)(nil) diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go index 1b10c0a0e..f3794e745 100644 --- a/backend/crypt/crypt.go +++ b/backend/crypt/crypt.go @@ -917,6 +917,16 @@ func (f *Fs) Disconnect(ctx context.Context) error { return do(ctx) } +// Shutdown the backend, closing any background tasks and any +// cached connections. +func (f *Fs) Shutdown(ctx context.Context) error { + do := f.Fs.Features().Shutdown + if do == nil { + return nil + } + return do(ctx) +} + // ObjectInfo describes a wrapped fs.ObjectInfo for being the source // // This encrypts the remote name and adjusts the size @@ -1025,6 +1035,7 @@ var ( _ fs.PublicLinker = (*Fs)(nil) _ fs.UserInfoer = (*Fs)(nil) _ fs.Disconnecter = (*Fs)(nil) + _ fs.Shutdowner = (*Fs)(nil) _ fs.ObjectInfo = (*ObjectInfo)(nil) _ fs.Object = (*Object)(nil) _ fs.ObjectUnWrapper = (*Object)(nil) diff --git a/backend/union/union.go b/backend/union/union.go index be8f941ec..5009ee28c 100644 --- a/backend/union/union.go +++ b/backend/union/union.go @@ -754,6 +754,20 @@ func (f *Fs) mergeDirEntries(entriesList [][]upstream.Entry) (fs.DirEntries, err return entries, nil } +// Shutdown the backend, closing any background tasks and any +// cached connections. +func (f *Fs) Shutdown(ctx context.Context) error { + errs := Errors(make([]error, len(f.upstreams))) + multithread(len(f.upstreams), func(i int) { + u := f.upstreams[i] + if do := u.Features().Shutdown; do != nil { + err := do(ctx) + errs[i] = errors.Wrap(err, u.Name()) + } + }) + return errs.Err() +} + // NewFs constructs an Fs from the path. // // The returned Fs is the actual Fs, referenced by remote in the config @@ -896,4 +910,5 @@ var ( _ fs.ChangeNotifier = (*Fs)(nil) _ fs.Abouter = (*Fs)(nil) _ fs.ListRer = (*Fs)(nil) + _ fs.Shutdowner = (*Fs)(nil) ) diff --git a/fs/fs.go b/fs/fs.go index 13ccde44e..c267b20b9 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -650,6 +650,10 @@ type Features struct { // If it is a string or a []string it will be shown to the user // otherwise it will be JSON encoded and shown to the user like that Command func(ctx context.Context, name string, arg []string, opt map[string]string) (interface{}, error) + + // Shutdown the backend, closing any background tasks and any + // cached connections. + Shutdown func(ctx context.Context) error } // Disable nil's out the named feature. If it isn't found then it @@ -774,6 +778,9 @@ func (ft *Features) Fill(ctx context.Context, f Fs) *Features { if do, ok := f.(Commander); ok { ft.Command = do.Command } + if do, ok := f.(Shutdowner); ok { + ft.Shutdown = do.Shutdown + } return ft.DisableList(GetConfig(ctx).DisableFeatures) } @@ -854,6 +861,9 @@ func (ft *Features) Mask(ctx context.Context, f Fs) *Features { ft.Disconnect = nil } // Command is always local so we don't mask it + if mask.Shutdown == nil { + ft.Shutdown = nil + } return ft.DisableList(GetConfig(ctx).DisableFeatures) } @@ -1099,6 +1109,13 @@ type Commander interface { Command(ctx context.Context, name string, arg []string, opt map[string]string) (interface{}, error) } +// Shutdowner is an interface to wrap the Shutdown function +type Shutdowner interface { + // Shutdown the backend, closing any background tasks and any + // cached connections. + Shutdown(ctx context.Context) error +} + // ObjectsChan is a channel of Objects type ObjectsChan chan Object