mirror of https://github.com/Xhofe/alist
* feat: add smb driver (close #1746) * Update driver.gopull/2122/head
parent
0019959eec
commit
dd4674e486
|
@ -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"
|
||||
|
|
|
@ -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)
|
|
@ -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{}
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package smb
|
|
@ -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)
|
||||
}
|
2
go.mod
2
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
|
||||
|
|
5
go.sum
5
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=
|
||||
|
|
Loading…
Reference in New Issue