diff --git a/drivers/all.go b/drivers/all.go index 42697b43..744919cc 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -5,6 +5,7 @@ import ( _ "github.com/Xhofe/alist/drivers/189" _ "github.com/Xhofe/alist/drivers/alidrive" _ "github.com/Xhofe/alist/drivers/alist" + _ "github.com/Xhofe/alist/drivers/ftp" _ "github.com/Xhofe/alist/drivers/google" _ "github.com/Xhofe/alist/drivers/lanzou" _ "github.com/Xhofe/alist/drivers/native" diff --git a/drivers/base/types.go b/drivers/base/types.go index a60f36de..567c0586 100644 --- a/drivers/base/types.go +++ b/drivers/base/types.go @@ -34,4 +34,5 @@ type Header struct{ type Link struct { Url string `json:"url"` Headers []Header `json:"headers"` + Data []byte } \ No newline at end of file diff --git a/drivers/ftp/driver.go b/drivers/ftp/driver.go new file mode 100644 index 00000000..43c01f2a --- /dev/null +++ b/drivers/ftp/driver.go @@ -0,0 +1,247 @@ +package ftp + +import ( + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/drivers/base" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/gin-gonic/gin" + "github.com/jlaffaye/ftp" + log "github.com/sirupsen/logrus" + "io/ioutil" + "path/filepath" +) + +type FTP struct{} + +func (driver FTP) Config() base.DriverConfig { + return base.DriverConfig{ + Name: "FTP", + OnlyProxy: true, + NoLink: true, + } +} + +func (driver FTP) Items() []base.Item { + return []base.Item{ + { + Name: "site_url", + Label: "ftp host url", + Type: base.TypeString, + Required: true, + }, + { + Name: "username", + Label: "username", + Type: base.TypeString, + Required: true, + }, + { + Name: "password", + Label: "password", + Type: base.TypeString, + Required: true, + }, + { + Name: "root_folder", + Label: "root folder path", + Type: base.TypeString, + Required: false, + }, + { + Name: "order_by", + Label: "order_by", + Type: base.TypeSelect, + Values: "name,size,updated_at", + Required: false, + }, + { + Name: "order_direction", + Label: "order_direction", + Type: base.TypeSelect, + Values: "ASC,DESC", + Required: false, + }, + } +} + +func (driver FTP) Save(account *model.Account, old *model.Account) error { + if account.RootFolder == "" { + account.RootFolder = "/" + } + conn, err := driver.Login(account) + if err != nil { + account.Status = err.Error() + } else { + account.Status = "work" + _ = conn.Quit() + } + _ = model.SaveAccount(account) + return err +} + +func (driver FTP) File(path string, account *model.Account) (*model.File, error) { + log.Debugf("file: %s", path) + path = utils.ParsePath(path) + if path == "/" { + return &model.File{ + Id: account.RootFolder, + Name: account.Name, + Size: 0, + Type: conf.FOLDER, + Driver: driver.Config().Name, + UpdatedAt: account.UpdatedAt, + }, nil + } + dir, name := filepath.Split(path) + files, err := driver.Files(dir, account) + if err != nil { + return nil, err + } + for _, file := range files { + if file.Name == name { + return &file, nil + } + } + return nil, base.ErrPathNotFound +} + +func (driver FTP) Files(path string, account *model.Account) ([]model.File, error) { + log.Debugf("files: %s", path) + path = utils.ParsePath(path) + cache, err := base.GetCache(path, account) + if err == nil { + files, _ := cache.([]model.File) + return files, nil + } + realPath := utils.Join(account.RootFolder, path) + conn, err := driver.Login(account) + if err != nil { + return nil, err + } + defer func() { _ = conn.Quit() }() + entries, err := conn.List(realPath) + if err != nil { + return nil, err + } + res := make([]model.File, 0) + for i, _ := range entries { + entry := entries[i] + f := model.File{ + Name: entry.Name, + Size: int64(entry.Size), + UpdatedAt: &entry.Time, + Driver: driver.Config().Name, + } + if entry.Type == ftp.EntryTypeFolder { + f.Type = conf.FOLDER + } else { + f.Type = utils.GetFileType(filepath.Ext(entry.Name)) + } + res = append(res, f) + } + if len(res) > 0 { + _ = base.SetCache(path, res, account) + } + return res, nil +} + +func (driver FTP) Link(path string, account *model.Account) (*base.Link, error) { + path = utils.ParsePath(path) + realPath := utils.Join(account.RootFolder, path) + conn, err := driver.Login(account) + if err != nil { + return nil, err + } + defer func() { _ = conn.Quit() }() + resp, err := conn.Retr(realPath) + if err != nil { + return nil, err + } + defer func() { _ = resp.Close() }() + data, err := ioutil.ReadAll(resp) + if err != nil { + return nil, err + } + return &base.Link{ + Data: data, + }, nil +} + +func (driver FTP) Path(path string, account *model.Account) (*model.File, []model.File, error) { + log.Debugf("ftp path: %s", path) + file, err := driver.File(path, account) + if err != nil { + return nil, nil, err + } + if !file.IsDir() { + //file.Url, _ = driver.Link(path, account) + return file, nil, nil + } + files, err := driver.Files(path, account) + if err != nil { + return nil, nil, err + } + model.SortFiles(files, account) + return nil, files, nil +} + +func (driver FTP) Proxy(c *gin.Context, account *model.Account) { + +} + +func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) { + return nil, base.ErrNotSupport +} + +func (driver FTP) MakeDir(path string, account *model.Account) error { + path = utils.ParsePath(path) + realPath := utils.Join(account.RootFolder, path) + conn, err := driver.Login(account) + if err != nil { + return err + } + defer func() { _ = conn.Quit() }() + return conn.MakeDir(realPath) +} + +func (driver FTP) Move(src string, dst string, account *model.Account) error { + //if utils.Dir(src) != utils.Dir(dst) { + // return base.ErrNotSupport + //} + realSrc := utils.Join(account.RootFolder, src) + realDst := utils.Join(account.RootFolder, dst) + conn, err := driver.Login(account) + if err != nil { + return err + } + defer func() { _ = conn.Quit() }() + return conn.Rename(realSrc, realDst) +} + +func (driver FTP) Copy(src string, dst string, account *model.Account) error { + return base.ErrNotSupport +} + +func (driver FTP) Delete(path string, account *model.Account) error { + path = utils.ParsePath(path) + realPath := utils.Join(account.RootFolder, path) + conn, err := driver.Login(account) + if err != nil { + return err + } + defer func() { _ = conn.Quit() }() + return conn.Delete(realPath) +} + +func (driver FTP) Upload(file *model.FileStream, account *model.Account) error { + realPath := utils.Join(account.RootFolder, file.ParentPath, file.Name) + conn, err := driver.Login(account) + if err != nil { + return err + } + defer func() { _ = conn.Quit() }() + return conn.Stor(realPath, file) +} + +var _ base.Driver = (*FTP)(nil) diff --git a/drivers/ftp/ftp.go b/drivers/ftp/ftp.go new file mode 100644 index 00000000..e9fb425d --- /dev/null +++ b/drivers/ftp/ftp.go @@ -0,0 +1,23 @@ +package ftp + +import ( + "github.com/Xhofe/alist/drivers/base" + "github.com/Xhofe/alist/model" + "github.com/jlaffaye/ftp" +) + +func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) { + conn, err := ftp.Connect(account.SiteUrl) + if err != nil { + return nil, err + } + err = conn.Login(account.Username, account.Password) + if err != nil { + return nil, err + } + return conn, nil +} + +func init() { + base.RegisterDriver(&FTP{}) +} \ No newline at end of file diff --git a/go.mod b/go.mod index a32d9822..151fdc23 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/jackc/pgx/v4 v4.13.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.2 // indirect + github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-isatty v0.0.14 // indirect diff --git a/go.sum b/go.sum index 9fa73462..9df5f657 100644 --- a/go.sum +++ b/go.sum @@ -266,6 +266,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b h1:Ur6QAxsHCK99Quj9PaWafoV4unb0DO/HWiKExD+TN5g= +github.com/jlaffaye/ftp v0.0.0-20211117213618-11820403398b/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= diff --git a/server/controllers/down.go b/server/controllers/down.go index 3bbe8f29..df120353 100644 --- a/server/controllers/down.go +++ b/server/controllers/down.go @@ -68,6 +68,10 @@ func Proxy(c *gin.Context) { common.ErrorResp(c, err, 500) return } + // 本机读取数据 + if account.Type == "FTP" { + c.Data(http.StatusOK, "application/octet-stream", link.Data) + } // 本机文件直接返回文件 if account.Type == "Native" { // 对于名称为index.html的文件需要特殊处理 diff --git a/server/controllers/path.go b/server/controllers/path.go index 14ae231e..405be4bc 100644 --- a/server/controllers/path.go +++ b/server/controllers/path.go @@ -88,20 +88,20 @@ func Link(c *gin.Context) { common.ErrorResp(c, err, 500) return } - link, err := driver.Link(path, account) - if err != nil { - common.ErrorResp(c, err, 500) - return - } if driver.Config().NoLink { common.SuccessResp(c, base.Link{ Url: fmt.Sprintf("//%s/d%s?d=1&sign=%s", c.Request.Host, req.Path, utils.SignWithToken(utils.Base(rawPath), conf.Token)), }) return - } else { - common.SuccessResp(c, link) + } + link, err := driver.Link(path, account) + if err != nil { + common.ErrorResp(c, err, 500) return } + common.SuccessResp(c, link) + return + } func Preview(c *gin.Context) { diff --git a/utils/file.go b/utils/file.go index 50cb36ec..354dc496 100644 --- a/utils/file.go +++ b/utils/file.go @@ -113,4 +113,12 @@ func Base(path string) string { return path } return path[idx+1:] +} + +func Join(elem ...string) string { + res := filepath.Join(elem...) + if res == "\\" { + res = "/" + } + return res } \ No newline at end of file