From dd4674e486c899c573711a447d54b2a1a98fdc1b Mon Sep 17 00:00:00 2001 From: BoYanZh Date: Tue, 25 Oct 2022 23:00:23 +0800 Subject: [PATCH] feat: add smb driver (close #1746) (#2114) * feat: add smb driver (close #1746) * Update driver.go --- drivers/all.go | 1 + drivers/smb/driver.go | 163 ++++++++++++++++++++++++++++++++++++++++++ drivers/smb/meta.go | 28 ++++++++ drivers/smb/types.go | 1 + drivers/smb/util.go | 122 +++++++++++++++++++++++++++++++ go.mod | 2 + go.sum | 5 ++ 7 files changed, 322 insertions(+) create mode 100644 drivers/smb/driver.go create mode 100644 drivers/smb/meta.go create mode 100644 drivers/smb/types.go create mode 100644 drivers/smb/util.go diff --git a/drivers/all.go b/drivers/all.go index 1a171306..bbfbf537 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -21,6 +21,7 @@ import ( _ "github.com/alist-org/alist/v3/drivers/quark" _ "github.com/alist-org/alist/v3/drivers/s3" _ "github.com/alist-org/alist/v3/drivers/sftp" + _ "github.com/alist-org/alist/v3/drivers/smb" _ "github.com/alist-org/alist/v3/drivers/teambition" _ "github.com/alist-org/alist/v3/drivers/thunder" _ "github.com/alist-org/alist/v3/drivers/uss" diff --git a/drivers/smb/driver.go b/drivers/smb/driver.go new file mode 100644 index 00000000..4589d5a4 --- /dev/null +++ b/drivers/smb/driver.go @@ -0,0 +1,163 @@ +package smb + +import ( + "context" + "errors" + "path/filepath" + + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/pkg/utils" + + "github.com/hirochachacha/go-smb2" +) + +type SMB struct { + model.Storage + Addition + fs *smb2.Share +} + +func (d *SMB) Config() driver.Config { + return config +} + +func (d *SMB) GetAddition() driver.Additional { + return d.Addition +} + +func (d *SMB) Init(ctx context.Context, storage model.Storage) error { + d.Storage = storage + err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition) + if err != nil { + return err + } + return d.initFS() +} + +func (d *SMB) Drop(ctx context.Context) error { + if d.fs != nil { + _ = d.fs.Umount() + } + return nil +} + +func (d *SMB) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + fullPath := d.getSMBPath(dir) + rawFiles, err := d.fs.ReadDir(fullPath) + if err != nil { + return nil, err + } + var files []model.Obj + for _, f := range rawFiles { + file := model.ObjThumb{ + Object: model.Object{ + Name: f.Name(), + Modified: f.ModTime(), + Size: f.Size(), + IsFolder: f.IsDir(), + }, + } + files = append(files, &file) + } + return files, nil +} + +//func (d *SMB) Get(ctx context.Context, path string) (model.Obj, error) { +// // this is optional +// return nil, errs.NotImplement +//} + +func (d *SMB) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + fullPath := d.getSMBPath(file) + remoteFile, err := d.fs.Open(fullPath) + if err != nil { + return nil, err + } + return &model.Link{ + Data: remoteFile, + }, nil +} + +func (d *SMB) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + fullPath := filepath.Join(d.getSMBPath(parentDir), dirName) + err := d.fs.MkdirAll(fullPath, 0700) + if err != nil { + return err + } + return nil +} + +func (d *SMB) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + srcPath := d.getSMBPath(srcObj) + dstPath := filepath.Join(d.getSMBPath(dstDir), srcObj.GetName()) + err := d.fs.Rename(srcPath, dstPath) + if err != nil { + return err + } + return nil +} + +func (d *SMB) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + srcPath := d.getSMBPath(srcObj) + dstPath := filepath.Join(filepath.Dir(srcPath), newName) + err := d.fs.Rename(srcPath, dstPath) + if err != nil { + return err + } + return nil +} + +func (d *SMB) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + srcPath := d.getSMBPath(srcObj) + dstPath := filepath.Join(d.getSMBPath(dstDir), srcObj.GetName()) + var err error + if srcObj.IsDir() { + err = d.CopyDir(srcPath, dstPath) + } else { + err = d.CopyFile(srcPath, dstPath) + } + if err != nil { + return err + } + return nil +} + +func (d *SMB) Remove(ctx context.Context, obj model.Obj) error { + var err error + fullPath := d.getSMBPath(obj) + if obj.IsDir() { + err = d.fs.RemoveAll(fullPath) + } else { + err = d.fs.Remove(fullPath) + } + if err != nil { + return err + } + return nil +} + +func (d *SMB) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { + fullPath := filepath.Join(d.getSMBPath(dstDir), stream.GetName()) + out, err := d.fs.Create(fullPath) + if err != nil { + return err + } + defer func() { + _ = out.Close() + if errors.Is(err, context.Canceled) { + _ = d.fs.Remove(fullPath) + } + }() + err = utils.CopyWithCtx(ctx, out, stream, stream.GetSize(), up) + if err != nil { + return err + } + return nil +} + +//func (d *SMB) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { +// return nil, errs.NotSupport +//} + +var _ driver.Driver = (*SMB)(nil) diff --git a/drivers/smb/meta.go b/drivers/smb/meta.go new file mode 100644 index 00000000..35d88911 --- /dev/null +++ b/drivers/smb/meta.go @@ -0,0 +1,28 @@ +package smb + +import ( + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/op" +) + +type Addition struct { + driver.RootPath + Address string `json:"address" required:"true"` + Username string `json:"username" required:"true"` + Password string `json:"password"` + ShareName string `json:"share_name" required:"true"` +} + +var config = driver.Config{ + Name: "SMB", + LocalSort: true, + OnlyLocal: true, + DefaultRoot: ".", + NoCache: true, +} + +func init() { + op.RegisterDriver(config, func() driver.Driver { + return &SMB{} + }) +} diff --git a/drivers/smb/types.go b/drivers/smb/types.go new file mode 100644 index 00000000..161798ad --- /dev/null +++ b/drivers/smb/types.go @@ -0,0 +1 @@ +package smb diff --git a/drivers/smb/util.go b/drivers/smb/util.go new file mode 100644 index 00000000..144ec171 --- /dev/null +++ b/drivers/smb/util.go @@ -0,0 +1,122 @@ +package smb + +import ( + "io" + "io/fs" + "net" + "os" + "path/filepath" + + "github.com/alist-org/alist/v3/internal/model" + "github.com/hirochachacha/go-smb2" +) + +func (d *SMB) initFS() error { + conn, err := net.Dial("tcp", d.Address) + if err != nil { + return err + } + dialer := &smb2.Dialer{ + Initiator: &smb2.NTLMInitiator{ + User: d.Username, + Password: d.Password, + }, + } + s, err := dialer.Dial(conn) + if err != nil { + return err + } + d.fs, err = s.Mount(d.ShareName) + if err != nil { + return err + } + return err +} + +func (d *SMB) getSMBPath(dir model.Obj) string { + fullPath := dir.GetPath() + if fullPath[0:1] != "." { + fullPath = "." + fullPath + } + return fullPath +} + +// CopyFile File copies a single file from src to dst +func (d *SMB) CopyFile(src, dst string) error { + var err error + var srcfd *smb2.File + var dstfd *smb2.File + var srcinfo fs.FileInfo + + if srcfd, err = d.fs.Open(src); err != nil { + return err + } + defer srcfd.Close() + + if dstfd, err = d.CreateNestedFile(dst); err != nil { + return err + } + defer dstfd.Close() + + if _, err = io.Copy(dstfd, srcfd); err != nil { + return err + } + if srcinfo, err = d.fs.Stat(src); err != nil { + return err + } + return d.fs.Chmod(dst, srcinfo.Mode()) +} + +// CopyDir Dir copies a whole directory recursively +func (d *SMB) CopyDir(src string, dst string) error { + var err error + var fds []fs.FileInfo + var srcinfo fs.FileInfo + + if srcinfo, err = d.fs.Stat(src); err != nil { + return err + } + if err = d.fs.MkdirAll(dst, srcinfo.Mode()); err != nil { + return err + } + if fds, err = d.fs.ReadDir(src); err != nil { + return err + } + for _, fd := range fds { + srcfp := filepath.Join(src, fd.Name()) + dstfp := filepath.Join(dst, fd.Name()) + + if fd.IsDir() { + if err = d.CopyDir(srcfp, dstfp); err != nil { + return err + } + } else { + if err = d.CopyFile(srcfp, dstfp); err != nil { + return err + } + } + } + return nil +} + +// Exists determine whether the file exists +func (d *SMB) Exists(name string) bool { + if _, err := d.fs.Stat(name); err != nil { + if os.IsNotExist(err) { + return false + } + } + return true +} + +// CreateNestedFile create nested file +func (d *SMB) CreateNestedFile(path string) (*smb2.File, error) { + basePath := filepath.Dir(path) + if !d.Exists(basePath) { + err := d.fs.MkdirAll(basePath, 0700) + if err != nil { + return nil, err + } + } + return d.fs.Create(path) +} diff --git a/go.mod b/go.mod index 25bc96b0..af38c0cc 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( require ( github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/geoffgarside/ber v1.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect @@ -42,6 +43,7 @@ require ( github.com/goccy/go-json v0.9.7 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hirochachacha/go-smb2 v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.12.1 // indirect diff --git a/go.sum b/go.sum index 1be3c684..fedd6037 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= +github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -67,6 +69,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= +github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= @@ -244,6 +248,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=