From 4d9a29bddddc1dfee053d90610298d9e34deab99 Mon Sep 17 00:00:00 2001 From: hcrgm Date: Sat, 11 Mar 2023 21:02:47 +0800 Subject: [PATCH] feat(ftp): support seek/range request (#3811) --- drivers/ftp/driver.go | 16 ++++---- drivers/ftp/util.go | 89 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/drivers/ftp/driver.go b/drivers/ftp/driver.go index 682d7ef4..2dc0d98f 100644 --- a/drivers/ftp/driver.go +++ b/drivers/ftp/driver.go @@ -4,6 +4,7 @@ import ( "context" stdpath "path" + "github.com/alist-org/alist/v3/drivers/base" "github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/errs" "github.com/alist-org/alist/v3/internal/model" @@ -44,8 +45,7 @@ func (d *FTP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]m return nil, err } res := make([]model.Obj, 0) - for i, _ := range entries { - entry := entries[i] + for _, entry := range entries { if entry.Name == "." || entry.Name == ".." { continue } @@ -64,13 +64,13 @@ func (d *FTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m if err := d.login(); err != nil { return nil, err } - resp, err := d.conn.Retr(file.GetPath()) - if err != nil { - return nil, err + + r := NewFTPFileReader(d.conn, file.GetPath()) + link := &model.Link{ + Data: r, } - return &model.Link{ - Data: resp, - }, nil + base.HandleRange(link, r, args.Header, file.GetSize()) + return link, nil } func (d *FTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { diff --git a/drivers/ftp/util.go b/drivers/ftp/util.go index 64660542..3abffd93 100644 --- a/drivers/ftp/util.go +++ b/drivers/ftp/util.go @@ -1,6 +1,13 @@ package ftp -import "github.com/jlaffaye/ftp" +import ( + "io" + "os" + "sync" + "time" + + "github.com/jlaffaye/ftp" +) // do others that not defined in Driver interface @@ -11,7 +18,7 @@ func (d *FTP) login() error { return nil } } - conn, err := ftp.Dial(d.Address) + conn, err := ftp.Dial(d.Address, ftp.DialWithShutTimeout(10*time.Second)) if err != nil { return err } @@ -22,3 +29,81 @@ func (d *FTP) login() error { d.conn = conn return nil } + +// An FTP file reader that implements io.ReadSeekCloser for seeking. +type FTPFileReader struct { + conn *ftp.ServerConn + resp *ftp.Response + offset int64 + mu sync.Mutex + path string +} + +func NewFTPFileReader(conn *ftp.ServerConn, path string) *FTPFileReader { + return &FTPFileReader{ + conn: conn, + path: path, + } +} + +func (r *FTPFileReader) Read(buf []byte) (n int, err error) { + r.mu.Lock() + defer r.mu.Unlock() + + if r.resp == nil { + r.resp, err = r.conn.RetrFrom(r.path, uint64(r.offset)) + if err != nil { + return 0, err + } + } + + n, err = r.resp.Read(buf) + r.offset += int64(n) + return +} + +func (r *FTPFileReader) Seek(offset int64, whence int) (int64, error) { + r.mu.Lock() + defer r.mu.Unlock() + + oldOffset := r.offset + var newOffset int64 + switch whence { + case io.SeekStart: + newOffset = offset + case io.SeekCurrent: + newOffset = oldOffset + offset + case io.SeekEnd: + size, err := r.conn.FileSize(r.path) + if err != nil { + return oldOffset, err + } + newOffset = offset + int64(size) + default: + return -1, os.ErrInvalid + } + + if newOffset < 0 { + // offset out of range + return oldOffset, os.ErrInvalid + } + if newOffset == oldOffset { + // offset not changed, so return directly + return oldOffset, nil + } + r.offset = newOffset + + if r.resp != nil { + // close the existing ftp data connection, otherwise the next read will be blocked + _ = r.resp.Close() // we do not care about whether it returns an error + r.resp = nil + } + return newOffset, nil +} + +func (r *FTPFileReader) Close() error { + if r.resp != nil { + return r.resp.Close() + } + return nil +}