2022-09-03 22:07:08 +08:00
|
|
|
package ftp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2025-08-06 13:32:37 +08:00
|
|
|
"io"
|
2022-09-03 22:07:08 +08:00
|
|
|
stdpath "path"
|
2025-08-06 13:32:37 +08:00
|
|
|
"sync"
|
|
|
|
"time"
|
2022-09-03 22:07:08 +08:00
|
|
|
|
2025-07-01 09:54:50 +08:00
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
2025-07-03 10:39:34 +08:00
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
2025-08-06 13:32:37 +08:00
|
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
2025-07-12 17:57:54 +08:00
|
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
2022-09-03 22:07:08 +08:00
|
|
|
"github.com/jlaffaye/ftp"
|
|
|
|
)
|
|
|
|
|
|
|
|
type FTP struct {
|
|
|
|
model.Storage
|
|
|
|
Addition
|
|
|
|
conn *ftp.ServerConn
|
2025-08-06 13:32:37 +08:00
|
|
|
|
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) Config() driver.Config {
|
|
|
|
return config
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) GetAddition() driver.Additional {
|
2022-12-13 18:03:30 +08:00
|
|
|
return &d.Addition
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
2022-12-13 18:03:30 +08:00
|
|
|
func (d *FTP) Init(ctx context.Context) error {
|
2025-08-06 13:32:37 +08:00
|
|
|
d.ctx, d.cancel = context.WithCancel(context.Background())
|
|
|
|
var err error
|
|
|
|
d.conn, err = d._login(ctx)
|
|
|
|
return err
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) Drop(ctx context.Context) error {
|
|
|
|
if d.conn != nil {
|
2025-08-06 13:32:37 +08:00
|
|
|
_ = d.conn.Quit()
|
|
|
|
d.cancel()
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
|
|
|
if err := d.login(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-06-16 16:58:02 +08:00
|
|
|
entries, err := d.conn.List(encode(dir.GetPath(), d.Encoding))
|
2022-09-03 22:07:08 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res := make([]model.Obj, 0)
|
2023-03-11 21:02:47 +08:00
|
|
|
for _, entry := range entries {
|
2022-09-03 22:07:08 +08:00
|
|
|
if entry.Name == "." || entry.Name == ".." {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
f := model.Object{
|
2024-06-16 16:58:02 +08:00
|
|
|
Name: decode(entry.Name, d.Encoding),
|
2022-09-03 22:07:08 +08:00
|
|
|
Size: int64(entry.Size),
|
|
|
|
Modified: entry.Time,
|
|
|
|
IsFolder: entry.Type == ftp.EntryTypeFolder,
|
|
|
|
}
|
|
|
|
res = append(res, &f)
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2025-08-06 13:32:37 +08:00
|
|
|
func (d *FTP) Link(_ context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
conn, err := d._login(ctx)
|
|
|
|
if err != nil {
|
|
|
|
cancel()
|
2022-09-03 22:07:08 +08:00
|
|
|
return nil, err
|
|
|
|
}
|
2025-08-06 13:32:37 +08:00
|
|
|
close := func() error {
|
|
|
|
_ = conn.Quit()
|
|
|
|
cancel()
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-11 21:02:47 +08:00
|
|
|
|
2025-08-06 13:32:37 +08:00
|
|
|
path := encode(file.GetPath(), d.Encoding)
|
|
|
|
size := file.GetSize()
|
|
|
|
mu := &sync.Mutex{}
|
|
|
|
resultRangeReader := func(context context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
|
|
|
length := httpRange.Length
|
|
|
|
if length < 0 || httpRange.Start+length > size {
|
|
|
|
length = size - httpRange.Start
|
|
|
|
}
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
r, err := conn.RetrFrom(path, uint64(httpRange.Start))
|
|
|
|
if err != nil {
|
|
|
|
_ = conn.Quit()
|
|
|
|
conn, err = d._login(ctx)
|
|
|
|
if err == nil {
|
|
|
|
r, err = conn.RetrFrom(path, uint64(httpRange.Start))
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r.SetDeadline(time.Now().Add(time.Second))
|
|
|
|
return &FileReader{
|
|
|
|
Response: r,
|
|
|
|
Reader: io.LimitReader(r, length),
|
|
|
|
ctx: context,
|
2025-07-12 17:57:54 +08:00
|
|
|
}, nil
|
|
|
|
}
|
2025-08-06 13:32:37 +08:00
|
|
|
|
2025-07-12 17:57:54 +08:00
|
|
|
return &model.Link{
|
2025-08-06 13:32:37 +08:00
|
|
|
RangeReader: &model.FileRangeReader{
|
|
|
|
RangeReaderIF: stream.RateLimitRangeReaderFunc(resultRangeReader),
|
2025-07-03 10:39:34 +08:00
|
|
|
},
|
2025-08-06 13:32:37 +08:00
|
|
|
SyncClosers: utils.NewSyncClosers(utils.CloseFunc(close)),
|
2025-07-12 17:57:54 +08:00
|
|
|
}, nil
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
|
|
|
if err := d.login(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-06-16 16:58:02 +08:00
|
|
|
return d.conn.MakeDir(encode(stdpath.Join(parentDir.GetPath(), dirName), d.Encoding))
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
|
|
if err := d.login(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-06-16 16:58:02 +08:00
|
|
|
return d.conn.Rename(
|
|
|
|
encode(srcObj.GetPath(), d.Encoding),
|
|
|
|
encode(stdpath.Join(dstDir.GetPath(), srcObj.GetName()), d.Encoding),
|
|
|
|
)
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
|
|
|
if err := d.login(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-06-16 16:58:02 +08:00
|
|
|
return d.conn.Rename(
|
|
|
|
encode(srcObj.GetPath(), d.Encoding),
|
|
|
|
encode(stdpath.Join(stdpath.Dir(srcObj.GetPath()), newName), d.Encoding),
|
|
|
|
)
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
|
|
return errs.NotSupport
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *FTP) Remove(ctx context.Context, obj model.Obj) error {
|
|
|
|
if err := d.login(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-06-16 16:58:02 +08:00
|
|
|
path := encode(obj.GetPath(), d.Encoding)
|
2022-09-03 22:07:08 +08:00
|
|
|
if obj.IsDir() {
|
2024-06-16 16:58:02 +08:00
|
|
|
return d.conn.RemoveDirRecur(path)
|
2022-09-03 22:07:08 +08:00
|
|
|
} else {
|
2024-06-16 16:58:02 +08:00
|
|
|
return d.conn.Delete(path)
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-01 17:29:55 +08:00
|
|
|
func (d *FTP) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer, up driver.UpdateProgress) error {
|
2022-09-03 22:07:08 +08:00
|
|
|
if err := d.login(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-02-01 17:29:55 +08:00
|
|
|
path := stdpath.Join(dstDir.GetPath(), s.GetName())
|
2025-02-16 12:22:11 +08:00
|
|
|
return d.conn.Stor(encode(path, d.Encoding), driver.NewLimitedUploadStream(ctx, &driver.ReaderUpdatingProgress{
|
|
|
|
Reader: s,
|
|
|
|
UpdateProgress: up,
|
|
|
|
}))
|
2022-09-03 22:07:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
var _ driver.Driver = (*FTP)(nil)
|