feat: add sftp driver (close #1466)

This commit is contained in:
Noah Hsu
2022-09-04 12:43:52 +08:00
parent 8fd56ef9dd
commit ffba5e0aec
9 changed files with 248 additions and 17 deletions

106
drivers/sftp/driver.go Normal file
View File

@ -0,0 +1,106 @@
package sftp
import (
"context"
"os"
"path"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/sftp"
)
type SFTP struct {
model.Storage
Addition
client *sftp.Client
}
func (d *SFTP) Config() driver.Config {
return config
}
func (d *SFTP) GetAddition() driver.Additional {
return d.Addition
}
func (d *SFTP) 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.initClient()
}
func (d *SFTP) Drop(ctx context.Context) error {
if d.client != nil {
_ = d.client.Close()
}
return nil
}
func (d *SFTP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
files, err := d.client.ReadDir(dir.GetPath())
if err != nil {
return nil, err
}
return utils.SliceConvert(files, func(src os.FileInfo) (model.Obj, error) {
return fileToObj(src), nil
})
}
//func (d *SFTP) Get(ctx context.Context, path string) (model.Obj, error) {
// // this is optional
// return nil, errs.NotImplement
//}
func (d *SFTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
remoteFile, err := d.client.Open(file.GetPath())
if err != nil {
return nil, err
}
return &model.Link{
Data: remoteFile,
}, nil
}
func (d *SFTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
return d.client.MkdirAll(path.Join(parentDir.GetPath(), dirName))
}
func (d *SFTP) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
return d.client.Rename(srcObj.GetPath(), path.Join(dstDir.GetPath(), srcObj.GetName()))
}
func (d *SFTP) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
return d.client.Rename(srcObj.GetPath(), path.Join(path.Dir(srcObj.GetPath()), newName))
}
func (d *SFTP) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
return errs.NotSupport
}
func (d *SFTP) Remove(ctx context.Context, obj model.Obj) error {
return d.remove(obj.GetPath())
}
func (d *SFTP) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
dstFile, err := d.client.Create(path.Join(dstDir.GetPath(), stream.GetName()))
if err != nil {
return err
}
defer func() {
_ = dstFile.Close()
}()
err = utils.CopyWithCtx(ctx, dstFile, stream, stream.GetSize(), up)
return err
}
func (d *SFTP) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
return nil, errs.NotSupport
}
var _ driver.Driver = (*SFTP)(nil)

30
drivers/sftp/meta.go Normal file
View File

@ -0,0 +1,30 @@
package sftp
import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/op"
)
type Addition struct {
Address string `json:"address" required:"true"`
Username string `json:"username" required:"true"`
PrivateKey string `json:"private_key" type:"text"`
Password string `json:"password"`
driver.RootFolderPath
}
var config = driver.Config{
Name: "SFTP",
LocalSort: true,
OnlyLocal: true,
DefaultRoot: "/",
CheckStatus: true,
}
func New() driver.Driver {
return &SFTP{}
}
func init() {
op.RegisterDriver(config, New)
}

16
drivers/sftp/types.go Normal file
View File

@ -0,0 +1,16 @@
package sftp
import (
"os"
"github.com/alist-org/alist/v3/internal/model"
)
func fileToObj(f os.FileInfo) model.Obj {
return &model.Object{
Name: f.Name(),
Size: f.Size(),
Modified: f.ModTime(),
IsFolder: f.IsDir(),
}
}

72
drivers/sftp/util.go Normal file
View File

@ -0,0 +1,72 @@
package sftp
import (
"path"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// do others that not defined in Driver interface
func (d *SFTP) initClient() error {
var auth ssh.AuthMethod
if d.PrivateKey != "" {
signer, err := ssh.ParsePrivateKey([]byte(d.PrivateKey))
if err != nil {
return err
}
auth = ssh.PublicKeys(signer)
} else {
auth = ssh.Password(d.Password)
}
config := &ssh.ClientConfig{
User: d.Username,
Auth: []ssh.AuthMethod{auth},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", d.Address, config)
if err != nil {
return err
}
d.client, err = sftp.NewClient(conn)
return err
}
func (d *SFTP) remove(remotePath string) error {
f, err := d.client.Stat(remotePath)
if err != nil {
return nil
}
if f.IsDir() {
return d.removeDirectory(remotePath)
} else {
return d.removeFile(remotePath)
}
}
func (d *SFTP) removeDirectory(remotePath string) error {
remoteFiles, err := d.client.ReadDir(remotePath)
if err != nil {
return err
}
for _, backupDir := range remoteFiles {
remoteFilePath := path.Join(remotePath, backupDir.Name())
if backupDir.IsDir() {
err := d.removeDirectory(remoteFilePath)
if err != nil {
return err
}
} else {
err := d.removeFile(remoteFilePath)
if err != nil {
return err
}
}
}
return d.client.RemoveDirectory(remotePath)
}
func (d *SFTP) removeFile(remotePath string) error {
return d.client.Remove(path.Join(remotePath))
}