From 28998d6f8c4f66a954892915b637231dd5b2ccd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=AE=E5=87=89?= <927625802@qq.com> Date: Mon, 6 Dec 2021 17:49:20 +0800 Subject: [PATCH] :construction: aliyundrive webdav write --- drivers/189/189.go | 2 +- drivers/alidrive/alidrive.go | 77 ++++++++++++++++++ drivers/alidrive/driver.go | 146 ++++++++++++++++++++++++++++++++-- drivers/base/cache.go | 25 ++++++ drivers/base/driver.go | 11 +-- drivers/base/types.go | 20 +++-- drivers/google/googledrive.go | 2 +- drivers/native/driver.go | 4 +- model/file_stream.go | 14 ++-- server/path.go | 2 +- server/webdav/file.go | 18 ++--- 11 files changed, 278 insertions(+), 43 deletions(-) create mode 100644 drivers/base/cache.go diff --git a/drivers/189/189.go b/drivers/189/189.go index 61562443..f8232792 100644 --- a/drivers/189/189.go +++ b/drivers/189/189.go @@ -52,7 +52,7 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File { //func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) { // dir, name := filepath.Split(path) // dir = utils.ParsePath(dir) -// _, _, err := c.Path(dir, account) +// _, _, err := c.ParentPath(dir, account) // if err != nil { // return nil, err // } diff --git a/drivers/alidrive/alidrive.go b/drivers/alidrive/alidrive.go index 0c4aafac..b0c8480f 100644 --- a/drivers/alidrive/alidrive.go +++ b/drivers/alidrive/alidrive.go @@ -1,6 +1,7 @@ package alidrive import ( + "errors" "fmt" "github.com/Xhofe/alist/conf" "github.com/Xhofe/alist/drivers/base" @@ -9,6 +10,7 @@ import ( "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" "path/filepath" + "strings" "time" ) @@ -156,6 +158,81 @@ func (driver AliDrive) RefreshToken(account *model.Account) error { return nil } +func (driver AliDrive) Rename(fileId, name string, account *model.Account) error { + var resp base.Json + var e AliRespError + _, err := aliClient.R().SetResult(&resp).SetError(&e). + SetHeader("authorization", "Bearer\t"+account.AccessToken). + SetBody(base.Json{ + "check_name_mode": "refuse", + "drive_id": account.DriveId, + "file_id": fileId, + "name": name, + }).Post("https://api.aliyundrive.com/v3/file/update") + if err != nil { + return err + } + if e.Code != "" { + if e.Code == "AccessTokenInvalid" { + err = driver.RefreshToken(account) + if err != nil { + return err + } else { + _ = model.SaveAccount(account) + return driver.Rename(fileId, name, account) + } + } + return fmt.Errorf("%s", e.Message) + } + if resp["name"] == name { + return nil + } + return fmt.Errorf("%+v", resp) +} + +func (driver AliDrive) Batch(srcId,dstId string, account *model.Account) error { + var e AliRespError + res, err := aliClient.R().SetError(&e). + SetHeader("authorization", "Bearer\t"+account.AccessToken). + SetBody(base.Json{ + "requests": []base.Json{ + { + "headers": base.Json{ + "Content-Type": "application/json", + }, + "method":"POST", + "id":srcId, + "body":base.Json{ + "drive_id": account.DriveId, + "file_id":srcId, + "to_drive_id":account.DriveId, + "to_parent_file_id":dstId, + }, + }, + }, + "resource": "file", + }).Post("https://api.aliyundrive.com/v3/batch") + if err != nil { + return err + } + if e.Code != "" { + if e.Code == "AccessTokenInvalid" { + err = driver.RefreshToken(account) + if err != nil { + return err + } else { + _ = model.SaveAccount(account) + return driver.Batch(srcId, dstId, account) + } + } + return fmt.Errorf("%s", e.Message) + } + if strings.Contains(res.String(), `"status":200`) { + return nil + } + return errors.New(res.String()) +} + func init() { base.RegisterDriver(&AliDrive{}) aliClient. diff --git a/drivers/alidrive/driver.go b/drivers/alidrive/driver.go index f4574bd7..116db4e4 100644 --- a/drivers/alidrive/driver.go +++ b/drivers/alidrive/driver.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/robfig/cron/v3" log "github.com/sirupsen/logrus" + "math" "path/filepath" ) @@ -16,7 +17,7 @@ type AliDrive struct{} func (driver AliDrive) Config() base.DriverConfig { return base.DriverConfig{ - Name: "AliDrive", + Name: "AliDrive", OnlyProxy: false, } } @@ -130,7 +131,7 @@ func (driver AliDrive) File(path string, account *model.Account) (*model.File, e func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, error) { path = utils.ParsePath(path) var rawFiles []AliFile - cache, err := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path)) + cache, err := base.GetCache(path, account) if err == nil { rawFiles, _ = cache.([]AliFile) } else { @@ -143,7 +144,7 @@ func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, return nil, err } if len(rawFiles) > 0 { - _ = conf.Cache.Set(conf.Ctx, fmt.Sprintf("%s%s", account.Name, path), rawFiles, nil) + _ = base.SetCache(path, rawFiles, account) } } files := make([]model.File, 0) @@ -249,23 +250,152 @@ func (driver AliDrive) Preview(path string, account *model.Account) (interface{} } func (driver AliDrive) MakeDir(path string, account *model.Account) error { - return base.ErrNotImplement + dir, name := filepath.Split(path) + parentFile, err := driver.File(dir, account) + if err != nil { + return err + } + if !parentFile.IsDir() { + return base.ErrNotFolder + } + var resp base.Json + var e AliRespError + _, err = aliClient.R().SetResult(&resp).SetError(&e). + SetHeader("authorization", "Bearer\t"+account.AccessToken). + SetBody(base.Json{ + "check_name_mode": "refuse", + "drive_id": account.DriveId, + "name": name, + "parent_file_id": parentFile.Id, + "type": "folder", + }).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") + if e.Code != "" { + if e.Code == "AccessTokenInvalid" { + err = driver.RefreshToken(account) + if err != nil { + return err + } else { + _ = model.SaveAccount(account) + return driver.MakeDir(path, account) + } + } + return fmt.Errorf("%s", e.Message) + } + if resp["file_name"] == name { + _ = base.DeleteCache(dir, account) + return nil + } + return fmt.Errorf("%+v", resp) } func (driver AliDrive) Move(src string, dst string, account *model.Account) error { - return base.ErrNotImplement + srcDir, _ := filepath.Split(src) + dstDir, dstName := filepath.Split(dst) + srcFile, err := driver.File(src, account) + if err != nil { + return err + } + // rename + if srcDir == dstDir { + err = driver.Rename(srcFile.Id, dstName, account) + } else { + // move + dstDirFile, err := driver.File(dstDir, account) + if err != nil { + return err + } + err = driver.Batch(srcFile.Id, dstDirFile.Id, account) + } + if err != nil { + _ = base.DeleteCache(srcDir, account) + _ = base.DeleteCache(dstDir, account) + } + return err } func (driver AliDrive) Copy(src string, dst string, account *model.Account) error { - return base.ErrNotImplement + return base.ErrNotSupport } func (driver AliDrive) Delete(path string, account *model.Account) error { - return base.ErrNotImplement + file, err := driver.File(path, account) + if err != nil { + return err + } + var resp base.Json + var e AliRespError + _, err = aliClient.R().SetResult(&resp).SetError(&e). + SetHeader("authorization", "Bearer\t"+account.AccessToken). + SetBody(base.Json{ + "drive_id": account.DriveId, + "file_id": file.Id, + }).Post("https://api.aliyundrive.com/v2/recyclebin/trash") + if e.Code != "" { + if e.Code == "AccessTokenInvalid" { + err = driver.RefreshToken(account) + if err != nil { + return err + } else { + _ = model.SaveAccount(account) + return driver.Delete(path, account) + } + } + return fmt.Errorf("%s", e.Message) + } + if resp["file_id"] == file.Id { + _ = base.DeleteCache(utils.Dir(path), account) + return nil + } + return fmt.Errorf("%+v", resp) +} + +type UploadResp struct { } func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error { + const DEFAULT int64 = 10485760 + var count = math.Ceil(float64(file.GetSize()) / float64(DEFAULT)) + //var finish int64 = 0 + parentFile, err := driver.File(file.ParentPath, account) + if err != nil { + return err + } + var resp UploadResp + var e AliRespError + partInfoList := make([]base.Json, 0) + for i := 0; i < int(count); i++ { + partInfoList = append(partInfoList, base.Json{ + "part_number": i + 1, + }) + } + _, err = aliClient.R().SetResult(&resp).SetError(&e). + SetHeader("authorization", "Bearer\t"+account.AccessToken). + SetBody(base.Json{ + "check_name_mode": "auto_rename", + // content_hash + "content_hash_name": "none", + "drive_id": account.DriveId, + "name": file.GetFileName(), + "parent_file_id": parentFile.Id, + "part_info_list": partInfoList, + //proof_code + "proof_version": "v1", + "size": file.GetSize(), + "type": "file", + }).Post("https://api.aliyundrive.com/v2/recyclebin/trash") + if e.Code != "" { + if e.Code == "AccessTokenInvalid" { + err = driver.RefreshToken(account) + if err != nil { + return err + } else { + _ = model.SaveAccount(account) + return driver.Upload(file, account) + } + } + return fmt.Errorf("%s", e.Message) + } return base.ErrNotImplement } -var _ base.Driver = (*AliDrive)(nil) \ No newline at end of file +var _ base.Driver = (*AliDrive)(nil) diff --git a/drivers/base/cache.go b/drivers/base/cache.go new file mode 100644 index 00000000..6deeb3be --- /dev/null +++ b/drivers/base/cache.go @@ -0,0 +1,25 @@ +package base + +import ( + "fmt" + "github.com/Xhofe/alist/conf" + "github.com/Xhofe/alist/model" + "github.com/Xhofe/alist/utils" +) + +func KeyCache(path string, account *model.Account) string { + path = utils.ParsePath(path) + return fmt.Sprintf("%s%s", account.Name, path) +} + +func SetCache(path string, obj interface{}, account *model.Account) error { + return conf.Cache.Set(conf.Ctx, KeyCache(path, account), obj, nil) +} + +func GetCache(path string, account *model.Account) (interface{}, error) { + return conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", KeyCache(path, account))) +} + +func DeleteCache(path string, account *model.Account) error { + return conf.Cache.Delete(conf.Ctx, fmt.Sprintf("%s%s", KeyCache(path, account))) +} diff --git a/drivers/base/driver.go b/drivers/base/driver.go index 8cdbaa52..35b5028c 100644 --- a/drivers/base/driver.go +++ b/drivers/base/driver.go @@ -41,11 +41,6 @@ type Item struct { Description string `json:"description"` } -type TokenResp struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` -} - var driversMap = map[string]Driver{} func RegisterDriver(driver Driver) { @@ -68,14 +63,14 @@ func GetDrivers() map[string][]Item { { Name: "proxy", Label: "proxy", - Type: "bool", + Type: TypeBool, Required: true, Description: "allow proxy", }, { Name: "webdav_proxy", Label: "webdav proxy", - Type: "bool", + Type: TypeBool, Required: true, Description: "Transfer the WebDAV of this account through the server", }, @@ -85,8 +80,6 @@ func GetDrivers() map[string][]Item { return res } -type Json map[string]interface{} - var NoRedirectClient *resty.Client func init() { diff --git a/drivers/base/types.go b/drivers/base/types.go index f2c7160c..7b4facba 100644 --- a/drivers/base/types.go +++ b/drivers/base/types.go @@ -1,12 +1,15 @@ package base -import "fmt" +import ( + "errors" +) var ( - ErrPathNotFound = fmt.Errorf("path not found") - ErrNotFile = fmt.Errorf("not file") - ErrNotImplement = fmt.Errorf("not implement") - ErrNotSupport = fmt.Errorf("not support") + ErrPathNotFound = errors.New("path not found") + ErrNotFile = errors.New("not file") + ErrNotImplement = errors.New("not implement") + ErrNotSupport = errors.New("not support") + ErrNotFolder = errors.New("not a folder") ) const ( @@ -15,3 +18,10 @@ const ( TypeBool = "bool" TypeNumber = "number" ) + +type Json map[string]interface{} + +type TokenResp struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` +} diff --git a/drivers/google/googledrive.go b/drivers/google/googledrive.go index 6e3c6833..226e5800 100644 --- a/drivers/google/googledrive.go +++ b/drivers/google/googledrive.go @@ -134,7 +134,7 @@ func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]GoogleF //func (driver GoogleDrive) GetFile(path string, account *model.Account) (*GoogleFile, error) { // dir, name := filepath.Split(path) // dir = utils.ParsePath(dir) -// _, _, err := driver.Path(dir, account) +// _, _, err := driver.ParentPath(dir, account) // if err != nil { // return nil, err // } diff --git a/drivers/native/driver.go b/drivers/native/driver.go index b65fc70e..b374fbb6 100644 --- a/drivers/native/driver.go +++ b/drivers/native/driver.go @@ -204,8 +204,8 @@ func (driver Native) Delete(path string, account *model.Account) error { } func (driver Native) Upload(file *model.FileStream, account *model.Account) error { - fullPath := filepath.Join(account.RootFolder, file.Path, file.Name) - _, err := driver.File(filepath.Join(file.Path,file.Name), account) + fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name) + _, err := driver.File(filepath.Join(file.ParentPath,file.Name), account) if err == nil { // TODO overwrite? } diff --git a/model/file_stream.go b/model/file_stream.go index 13325e3e..b1e9b3b0 100644 --- a/model/file_stream.go +++ b/model/file_stream.go @@ -3,11 +3,11 @@ package model import "io" type FileStream struct { - File io.ReadCloser - Size uint64 - Path string - Name string - MIMEType string + File io.ReadCloser + Size uint64 + ParentPath string + Name string + MIMEType string } func (file FileStream) Read(p []byte) (n int, err error) { @@ -30,6 +30,6 @@ func (file FileStream) GetFileName() string { return file.Name } -func (file FileStream) GetPath() string { - return file.Path +func (file FileStream) GetParentPath() string { + return file.ParentPath } \ No newline at end of file diff --git a/server/path.go b/server/path.go index f68a0ce3..4b3df0db 100644 --- a/server/path.go +++ b/server/path.go @@ -11,7 +11,7 @@ import ( ) type PathReq struct { - Path string `json:"Path"` + Path string `json:"ParentPath"` Password string `json:"Password"` } diff --git a/server/webdav/file.go b/server/webdav/file.go index 94f60290..16e991c5 100644 --- a/server/webdav/file.go +++ b/server/webdav/file.go @@ -23,16 +23,16 @@ import ( type FileSystem struct{} func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) { - var path, name string + var internalPath, name string switch model.AccountsCount() { case 0: return nil, "", nil, fmt.Errorf("no accounts,please add one first") case 1: - path = rawPath + internalPath = rawPath break default: paths := strings.Split(rawPath, "/") - path = "/" + strings.Join(paths[2:], "/") + internalPath = "/" + strings.Join(paths[2:], "/") name = paths[1] } account, ok := model.GetAccount(name) @@ -43,7 +43,7 @@ func ParsePath(rawPath string) (*model.Account, string, base.Driver, error) { if !ok { return nil, "", nil, fmt.Errorf("no [%s] driver", account.Type) } - return &account, path, driver, nil + return &account, internalPath, driver, nil } func (fs *FileSystem) File(rawPath string) (*model.File, error) { @@ -160,11 +160,11 @@ func (fs *FileSystem) Upload(ctx context.Context, r *http.Request, rawPath strin } filePath, fileName := filepath.Split(path_) fileData := model.FileStream{ - MIMEType: r.Header.Get("Content-Type"), - File: r.Body, - Size: fileSize, - Name: fileName, - Path: filePath, + MIMEType: r.Header.Get("Content-Type"), + File: r.Body, + Size: fileSize, + Name: fileName, + ParentPath: filePath, } return driver.Upload(&fileData, account) }