From 47aada16a04579a8bc1fc7946b1f5cc7300c6b16 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood <nick@craig-wood.com>
Date: Fri, 27 Nov 2020 17:02:00 +0000
Subject: [PATCH] fs: add Shutdown optional method for backends

---
 backend/cache/cache.go       | 11 +++++++++++
 backend/chunker/chunker.go   | 11 +++++++++++
 backend/compress/compress.go | 11 +++++++++++
 backend/crypt/crypt.go       | 11 +++++++++++
 backend/union/union.go       | 15 +++++++++++++++
 fs/fs.go                     | 17 +++++++++++++++++
 6 files changed, 76 insertions(+)

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