From d71ed4d775c391848af9322b14b1de251c0d4c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=87=89?= <927625802@qq.com> Date: Thu, 30 Dec 2021 21:39:17 +0800 Subject: [PATCH] :sparkler: support webdav driver --- README.md | 1 + README_cn.md | 1 + drivers/all.go | 1 + drivers/base/driver.go | 4 +- drivers/base/types.go | 11 ++- drivers/ftp/driver.go | 19 ++-- drivers/native/driver.go | 2 +- drivers/webdav/driver.go | 194 +++++++++++++++++++++++++++++++++++++ drivers/webdav/webdav.go | 23 +++++ go.mod | 1 + go.sum | 2 + server/controllers/down.go | 14 ++- server/controllers/path.go | 2 +- 13 files changed, 255 insertions(+), 20 deletions(-) create mode 100644 drivers/webdav/driver.go create mode 100644 drivers/webdav/webdav.go diff --git a/README.md b/README.md index 855ad842..6a413d6d 100755 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ English | [中文](./README_cn.md) - [x] [PikPak](https://www.mypikpak.com/) - [x] [ShandianPan](https://shandianpan.com/) - [x] [S3](https://aws.amazon.com/s3/) + - [x] WebDav - [x] File preview (PDF, markdown, code, plain text, ...) - [x] Image preview in gallery mode - [x] Video and audio preview (mp4, mp3, ...) diff --git a/README_cn.md b/README_cn.md index ff3ddb1e..f004728e 100644 --- a/README_cn.md +++ b/README_cn.md @@ -30,6 +30,7 @@ - [x] [PikPak](https://www.mypikpak.com/) - [x] [闪电盘](https://shandianpan.com/) - [x] [S3](https://aws.amazon.com/cn/s3/) + - [x] WebDav - [x] 文件预览(PDF、markdown、代码、纯文本……) - [x] 画廊模式下的图像预览 - [x] 视频和音频预览(mp4、mp3 等) diff --git a/drivers/all.go b/drivers/all.go index 4b875781..fd12981f 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -13,4 +13,5 @@ import ( _ "github.com/Xhofe/alist/drivers/pikpak" _ "github.com/Xhofe/alist/drivers/s3" _ "github.com/Xhofe/alist/drivers/shandian" + _ "github.com/Xhofe/alist/drivers/webdav" ) diff --git a/drivers/base/driver.go b/drivers/base/driver.go index f7206688..6a703d31 100644 --- a/drivers/base/driver.go +++ b/drivers/base/driver.go @@ -11,8 +11,8 @@ import ( type DriverConfig struct { Name string - OnlyProxy bool - NoLink bool // 必须本机返回的 + OnlyProxy bool // 必须使用代理(本机或者其他机器) + OnlyLocal bool // 必须本机返回的 ApiProxy bool // 使用API中转的 NoNeedSetLink bool // 不需要设置链接的 NoCors bool // 不可以跨域 diff --git a/drivers/base/types.go b/drivers/base/types.go index 102d8fff..b59e3070 100644 --- a/drivers/base/types.go +++ b/drivers/base/types.go @@ -2,6 +2,7 @@ package base import ( "errors" + "io" ) var ( @@ -34,13 +35,13 @@ type TokenResp struct { RefreshToken string `json:"refresh_token"` } -type Header struct{ - Name string `json:"name"` +type Header struct { + Name string `json:"name"` Value string `json:"value"` } type Link struct { - Url string `json:"url"` + Url string `json:"url"` Headers []Header `json:"headers"` - Data []byte -} \ No newline at end of file + Data io.ReadCloser +} diff --git a/drivers/ftp/driver.go b/drivers/ftp/driver.go index f99efc20..9e03efba 100644 --- a/drivers/ftp/driver.go +++ b/drivers/ftp/driver.go @@ -8,7 +8,6 @@ import ( "github.com/gin-gonic/gin" "github.com/jlaffaye/ftp" log "github.com/sirupsen/logrus" - "io/ioutil" "path/filepath" ) @@ -18,7 +17,7 @@ func (driver FTP) Config() base.DriverConfig { return base.DriverConfig{ Name: "FTP", OnlyProxy: true, - NoLink: true, + OnlyLocal: true, NoNeedSetLink: true, LocalSort: true, } @@ -150,13 +149,13 @@ func (driver FTP) Link(args base.Args, account *model.Account) (*base.Link, erro if err != nil { return nil, err } - defer func() { _ = resp.Close() }() - data, err := ioutil.ReadAll(resp) - 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, + Data: resp, }, nil } @@ -214,7 +213,9 @@ func (driver FTP) Move(src string, dst string, account *model.Account) error { err = conn.Rename(realSrc, realDst) if err != nil { _ = base.DeleteCache(utils.Dir(src), account) - _ = base.DeleteCache(utils.Dir(dst), account) + if utils.Dir(src) != utils.Dir(dst) { + _ = base.DeleteCache(utils.Dir(dst), account) + } } return err } diff --git a/drivers/native/driver.go b/drivers/native/driver.go index 4e3df9d1..6dafe00f 100644 --- a/drivers/native/driver.go +++ b/drivers/native/driver.go @@ -21,7 +21,7 @@ func (driver Native) Config() base.DriverConfig { return base.DriverConfig{ Name: "Native", OnlyProxy: true, - NoLink: true, + OnlyLocal: true, NoNeedSetLink: true, LocalSort: true, } diff --git a/drivers/webdav/driver.go b/drivers/webdav/driver.go new file mode 100644 index 00000000..6908762e --- /dev/null +++ b/drivers/webdav/driver.go @@ -0,0 +1,194 @@ +package webdav + +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" + "path/filepath" +) + +type WebDav struct{} + +func (driver WebDav) Config() base.DriverConfig { + return base.DriverConfig{ + Name: "WebDav", + OnlyProxy: true, + OnlyLocal: true, + NoNeedSetLink: true, + LocalSort: true, + } +} + +func (driver WebDav) Items() []base.Item { + return []base.Item{ + { + Name: "site_url", + Label: "webdav root url", + Type: base.TypeString, + Required: true, + }, + { + Name: "username", + Label: "username", + Type: base.TypeString, + Required: true, + }, + { + Name: "password", + Label: "password", + Type: base.TypeString, + Required: true, + }, + } +} + +func (driver WebDav) Save(account *model.Account, old *model.Account) error { + account.Status = "work" + _ = model.SaveAccount(account) + return nil +} + +func (driver WebDav) File(path string, account *model.Account) (*model.File, error) { + if path == "/" { + return &model.File{ + Id: "/", + 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 WebDav) Files(path string, account *model.Account) ([]model.File, error) { + path = utils.ParsePath(path) + cache, err := base.GetCache(path, account) + if err == nil { + files, _ := cache.([]model.File) + return files, nil + } + c := driver.NewClient(account) + rawFiles, err := c.ReadDir(driver.WebDavPath(path)) + if err != nil { + return nil, err + } + files := make([]model.File, 0) + if len(rawFiles) == 0 { + return files, nil + } + for _, f := range rawFiles { + t := f.ModTime() + file := model.File{ + Name: f.Name(), + Size: f.Size(), + Driver: driver.Config().Name, + UpdatedAt: &t, + } + if f.IsDir() { + file.Type = conf.FOLDER + } else { + file.Type = utils.GetFileType(filepath.Ext(f.Name())) + } + files = append(files, file) + } + _ = base.SetCache(path, files, account) + return files, nil +} + +func (driver WebDav) Link(args base.Args, account *model.Account) (*base.Link, error) { + path := args.Path + c := driver.NewClient(account) + reader, err := c.ReadStream(driver.WebDavPath(path)) + if err != nil { + return nil, err + } + return &base.Link{Data: reader}, nil +} + +func (driver WebDav) Path(path string, account *model.Account) (*model.File, []model.File, error) { + file, err := driver.File(path, account) + if err != nil { + return nil, nil, err + } + if !file.IsDir() { + return file, nil, nil + } + files, err := driver.Files(path, account) + if err != nil { + return nil, nil, err + } + return nil, files, nil +} + +func (driver WebDav) Proxy(c *gin.Context, account *model.Account) { + +} + +func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) { + return nil, base.ErrNotSupport +} + +func (driver WebDav) MakeDir(path string, account *model.Account) error { + c := driver.NewClient(account) + err := c.MkdirAll(driver.WebDavPath(path), 0644) + if err == nil { + _ = base.DeleteCache(utils.Dir(path), account) + } + return err +} + +func (driver WebDav) Move(src string, dst string, account *model.Account) error { + c := driver.NewClient(account) + err := c.Rename(driver.WebDavPath(src), driver.WebDavPath(dst), true) + if err == nil { + _ = base.DeleteCache(utils.Dir(src), account) + if utils.Dir(src) != utils.Dir(dst) { + _ = base.DeleteCache(utils.Dir(dst), account) + } + } + return err +} + +func (driver WebDav) Copy(src string, dst string, account *model.Account) error { + c := driver.NewClient(account) + err := c.Copy(driver.WebDavPath(src), driver.WebDavPath(dst), true) + if err == nil { + _ = base.DeleteCache(utils.Dir(dst), account) + } + return err +} + +func (driver WebDav) Delete(path string, account *model.Account) error { + c := driver.NewClient(account) + err := c.RemoveAll(driver.WebDavPath(path)) + if err == nil { + _ = base.DeleteCache(utils.Dir(path), account) + } + return err +} + +func (driver WebDav) Upload(file *model.FileStream, account *model.Account) error { + c := driver.NewClient(account) + path := utils.Join(file.ParentPath, file.Name) + err := c.WriteStream(driver.WebDavPath(path), file, 0644) + if err == nil { + _ = base.DeleteCache(utils.Dir(file.ParentPath), account) + } + return err +} + +var _ base.Driver = (*WebDav)(nil) diff --git a/drivers/webdav/webdav.go b/drivers/webdav/webdav.go new file mode 100644 index 00000000..0af95c8b --- /dev/null +++ b/drivers/webdav/webdav.go @@ -0,0 +1,23 @@ +package webdav + +import ( + "github.com/Xhofe/alist/drivers/base" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" + "github.com/studio-b12/gowebdav" + "strings" +) + +func (driver WebDav) NewClient(account *model.Account) *gowebdav.Client { + return gowebdav.NewClient(account.SiteUrl, account.Username, account.Password) +} + +func (driver WebDav) WebDavPath(path string) string { + path = utils.ParsePath(path) + path = strings.TrimPrefix(path, "/") + return path +} + +func init() { + base.RegisterDriver(&WebDav{}) +} diff --git a/go.mod b/go.mod index 28b6ee9d..a3b37c1f 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( github.com/prometheus/common v0.18.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect github.com/spf13/cast v1.3.1 // indirect + github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f // indirect github.com/ugorji/go/codec v1.2.6 // indirect go.opentelemetry.io/otel v0.20.0 // indirect go.opentelemetry.io/otel/metric v0.20.0 // indirect diff --git a/go.sum b/go.sum index 4642b1cf..ec296457 100644 --- a/go.sum +++ b/go.sum @@ -486,6 +486,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f h1:L2NE7BXnSlSLoNYZ0lCwZDjdnYjCNYC71k9ClZUTFTs= +github.com/studio-b12/gowebdav v0.0.0-20211109083228-3f8721cd4b6f/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= diff --git a/server/controllers/down.go b/server/controllers/down.go index fefdc862..cb29c0bf 100644 --- a/server/controllers/down.go +++ b/server/controllers/down.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" + "io" "net/http" "net/http/httputil" "net/url" @@ -78,8 +79,17 @@ func Proxy(c *gin.Context) { return } // 本机读取数据 - if account.Type == "FTP" { - c.Data(http.StatusOK, "application/octet-stream", link.Data) + if link.Data != nil { + //c.Data(http.StatusOK, "application/octet-stream", link.Data) + defer func() { + _ = link.Data.Close() + }() + c.Status(http.StatusOK) + c.Header("content", "application/octet-stream") + _, err = io.Copy(c.Writer, link.Data) + if err != nil { + _, _ = c.Writer.WriteString(err.Error()) + } return } // 本机文件直接返回文件 diff --git a/server/controllers/path.go b/server/controllers/path.go index 85a02867..4f4affe9 100644 --- a/server/controllers/path.go +++ b/server/controllers/path.go @@ -104,7 +104,7 @@ func Link(c *gin.Context) { common.ErrorResp(c, err, 500) return } - if driver.Config().NoLink { + if driver.Config().OnlyLocal { common.SuccessResp(c, base.Link{ Url: fmt.Sprintf("//%s/p%s?d=1&sign=%s", c.Request.Host, req.Path, utils.SignWithToken(utils.Base(rawPath), conf.Token)), })