From 39f8fc66aa570327de2d6801f4d535fa2ea5723f Mon Sep 17 00:00:00 2001 From: Mickael Kerjean Date: Mon, 10 Apr 2023 01:28:39 +1000 Subject: [PATCH] fix (ftp): recreate connection if closed An issue would araise when the connection is closed before we had time to do the vacuum cleaning on servers like on infinity free where "Our server has quite aggressive inactivity timeouts and will kill the connection after only 20 seconds of inactivity." reference: https://forum.infinityfree.net/t/good-online-ftp/69285/18 --- server/plugin/plg_backend_ftp/index.go | 134 +++++++++++++++++-------- 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/server/plugin/plg_backend_ftp/index.go b/server/plugin/plg_backend_ftp/index.go index 519820bc..f714798e 100644 --- a/server/plugin/plg_backend_ftp/index.go +++ b/server/plugin/plg_backend_ftp/index.go @@ -1,6 +1,7 @@ package plg_backend_ftp import ( + "context" "crypto/tls" "fmt" . "github.com/mickael-kerjean/filestash/server/common" @@ -47,7 +48,7 @@ func (f Ftp) Init(params map[string]string, app *App) (IBackend, error) { if c := FtpCache.Get(params); c != nil { d := c.(*Ftp) if d == nil { - Log.Warning("plg_backend_ftp::sftp is nil on get") + Log.Warning("plg_backend_ftp::ftp is nil on get") return nil, ErrInternal } else if d.wg == nil { Log.Warning("plg_backend_ftp::wg is nil on get") @@ -246,31 +247,50 @@ func (f Ftp) Meta(path string) Metadata { return Metadata{} } -func (f Ftp) Home() (string, error) { - return f.client.Getwd() +func (f Ftp) Home() (home string, err error) { + f.Execute(func(client *goftp.Client) error { + home, err = f.client.Getwd() + return err + }) + return home, err } -func (f Ftp) Ls(path string) ([]os.FileInfo, error) { - return f.client.ReadDir(path) +func (f Ftp) Ls(path string) (files []os.FileInfo, err error) { + f.Execute(func(client *goftp.Client) error { + files, err = client.ReadDir(path) + return err + }) + return files, err } -func (f Ftp) Cat(path string) (io.ReadCloser, error) { - pr, pw := io.Pipe() - go func() { - if err := f.client.Retrieve(path, pw); err != nil { - pr.CloseWithError(NewError("Problem", 409)) +func (f Ftp) Cat(path string) (reader io.ReadCloser, err error) { + f.Execute(func(client *goftp.Client) error { + if _, err = client.Stat(path); err != nil { + return err } - pw.Close() - }() - return pr, nil + pr, pw := io.Pipe() + go func() { + err = client.Retrieve(path, pw) + if err != nil { + pr.CloseWithError(NewError("Problem", 409)) + } + pw.Close() + }() + reader = pr + return nil + }) + return reader, err } -func (f Ftp) Mkdir(path string) error { - _, err := f.client.Mkdir(path) +func (f Ftp) Mkdir(path string) (err error) { + f.Execute(func(client *goftp.Client) error { + _, err = client.Mkdir(path) + return err + }) return err } -func (f Ftp) Rm(path string) error { +func (f Ftp) Rm(path string) (err error) { isDirectory := func(p string) bool { return regexp.MustCompile(`\/$`).MatchString(p) } @@ -287,43 +307,77 @@ func (f Ftp) Rm(path string) error { } return e } - if isDirectory(path) { - entries, err := f.Ls(path) - if transformError(err) != nil { - return err - } - for _, entry := range entries { - if entry.IsDir() { - err = f.Rm(path + entry.Name() + "/") - if transformError(err) != nil { - return err - } - } else { - err = f.Rm(path + entry.Name()) - if transformError(err) != nil { - return err + var recursiveDelete func(client *goftp.Client, _path string) error + recursiveDelete = func(client *goftp.Client, _path string) error { + if isDirectory(_path) { + entries, err := client.ReadDir(_path) + if transformError(err) != nil { + return err + } + for _, entry := range entries { + if entry.IsDir() { + err = recursiveDelete(client, _path+entry.Name()+"/") + if transformError(err) != nil { + return err + } + } else { + err = recursiveDelete(client, _path+entry.Name()) + if transformError(err) != nil { + return err + } } } + err = client.Rmdir(_path) + return transformError(err) } - err = f.client.Rmdir(path) + err = client.Delete(_path) return transformError(err) } - err := f.client.Delete(path) - return transformError(err) + f.Execute(func(client *goftp.Client) error { + err = recursiveDelete(client, path) + return err + }) + return err } -func (f Ftp) Mv(from string, to string) error { - return f.client.Rename(from, to) +func (f Ftp) Mv(from string, to string) (err error) { + f.Execute(func(client *goftp.Client) error { + err = client.Rename(from, to) + return err + }) + return err } -func (f Ftp) Touch(path string) error { - return f.client.Store(path, strings.NewReader("")) +func (f Ftp) Touch(path string) (err error) { + f.Execute(func(client *goftp.Client) error { + err = client.Store(path, strings.NewReader("")) + return err + }) + return err } -func (f Ftp) Save(path string, file io.Reader) error { - return f.client.Store(path, file) +func (f Ftp) Save(path string, file io.Reader) (err error) { + f.Execute(func(client *goftp.Client) error { + err = client.Store(path, file) + return err + }) + return err } func (f Ftp) Close() error { return f.client.Close() } + +func (f Ftp) Execute(fn func(*goftp.Client) error) { + err := fn(f.client) + if ftpErr, ok := err.(goftp.Error); ok { + code := ftpErr.Code() + if code == 421 || (code == 0 && err.Error() == "error reading response: EOF") { + f.Close() + FtpCache.Set(f.p, nil) + if b, err := f.Init(f.p, &App{Context: context.Background()}); err == nil { + fn(b.(*Ftp).client) + } + } + } +}