From 346a56027730bcfaeb1ea6fc437d85bafd1d918a Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 24 Dec 2025 16:15:31 +0800 Subject: [PATCH 1/7] feat(drivers): add aliyunfile Signed-off-by: MadDogOwner --- drivers/aliyunfile/driver.go | 344 +++++++++++++++++++++++++++++++++++ drivers/aliyunfile/help.go | 66 +++++++ drivers/aliyunfile/meta.go | 28 +++ drivers/aliyunfile/types.go | 133 ++++++++++++++ drivers/aliyunfile/util.go | 144 +++++++++++++++ drivers/all.go | 1 + 6 files changed, 716 insertions(+) create mode 100644 drivers/aliyunfile/driver.go create mode 100644 drivers/aliyunfile/help.go create mode 100644 drivers/aliyunfile/meta.go create mode 100644 drivers/aliyunfile/types.go create mode 100644 drivers/aliyunfile/util.go diff --git a/drivers/aliyunfile/driver.go b/drivers/aliyunfile/driver.go new file mode 100644 index 000000000..a5c90280d --- /dev/null +++ b/drivers/aliyunfile/driver.go @@ -0,0 +1,344 @@ +package aliyunfile + +import ( + "bytes" + "context" + "crypto/sha1" + "encoding/hex" + "fmt" + "io" + "math" + "net/http" + "os" + "time" + + "github.com/OpenListTeam/OpenList/v4/drivers/base" + "github.com/OpenListTeam/OpenList/v4/internal/conf" + "github.com/OpenListTeam/OpenList/v4/internal/driver" + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" + "github.com/OpenListTeam/OpenList/v4/internal/stream" + "github.com/OpenListTeam/OpenList/v4/pkg/cron" + "github.com/OpenListTeam/OpenList/v4/pkg/utils" + "github.com/go-resty/resty/v2" + log "github.com/sirupsen/logrus" +) + +type AliCDE struct { + model.Storage + Addition + AccessToken string + ApiEndpoint string + AuthEndpoint string + UIEndpoint string + cron *cron.Cron +} + +func (d *AliCDE) Config() driver.Config { + return config +} + +func (d *AliCDE) GetAddition() driver.Additional { + return &d.Addition +} + +func (d *AliCDE) Init(ctx context.Context) error { + // get entrypoint + var endp EndpointResp + _, err, _ := d.request("https://web-sv.aliyunpds.com/endpoint/get_endpoints", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{"domain_id": d.Addition.DomainID, "is_vpc": false, "product_type": "edm", "ignoreError": true}) + }, &endp) + if err != nil { + return err + } + d.ApiEndpoint = endp.APIEndpoint + d.AuthEndpoint = endp.AuthEndpoint + d.UIEndpoint = endp.UIEndpoint + + err = d.refreshToken() + if err != nil { + return err + } + // get driver id + if d.DriveID == "" { + var userp UserResp + _, err, _ = d.request(d.ApiEndpoint+"/v2/user/get", http.MethodPost, nil, &userp) + if err != nil { + return err + } + d.DriveID = userp.DefaultDriveID + op.MustSaveDriverStorage(d) + } + d.cron = cron.NewCron(time.Hour * 2) + d.cron.Do(func() { + err := d.refreshToken() + if err != nil { + log.Errorf("%+v", err) + } + }) + return nil +} + +func (d *AliCDE) Drop(ctx context.Context) error { + if d.cron != nil { + d.cron.Stop() + } + return nil +} + +func (d *AliCDE) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + files, err := d.getFiles(dir.GetID()) + if err != nil { + return nil, err + } + return utils.SliceConvert(files, func(src File) (model.Obj, error) { + return fileToObj(src), nil + }) +} + +func (d *AliCDE) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + data := base.Json{ + "drive_id": d.DriveID, + "file_id": file.GetID(), + "file_name": file.GetName(), + "expire_sec": 7200, + } + res, err, _ := d.request(d.ApiEndpoint+"/v2/file/get_download_url", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + if err != nil { + return nil, err + } + var exp time.Duration + parsedTime, _ := time.Parse(time.RFC3339, utils.Json.Get(res, "expiration").ToString()) + exp = time.Until(parsedTime) - time.Minute + return &model.Link{ + Header: http.Header{ + "Referer": []string{d.UIEndpoint + "/"}, + }, + URL: utils.Json.Get(res, "url").ToString(), + Expiration: &exp, + }, nil +} + +func (d *AliCDE) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + _, err, _ := d.request(d.ApiEndpoint+"/v2/file/create", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "check_name_mode": "refuse", + "drive_id": d.DriveID, + "name": dirName, + "parent_file_id": parentDir.GetID(), + "type": "folder", + "actionType": "folder", + }) + }, nil) + return err +} + +func (d *AliCDE) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + _, err, _ := d.request(d.ApiEndpoint+"/v2/file/move", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "auto_rename": true, + "drive_id": d.DriveID, + "file_id": srcObj.GetID(), + "to_drive_id": d.DriveID, + "to_parent_file_id": dstDir.GetID(), + }) + }, nil) + return err +} + +func (d *AliCDE) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + _, err, _ := d.request(d.ApiEndpoint+"/v2/file/update", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "check_name_mode": "refuse", + "drive_id": d.DriveID, + "file_id": srcObj.GetID(), + "name": newName, + }) + }, nil) + return err +} + +func (d *AliCDE) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + _, err, _ := d.request(d.ApiEndpoint+"/v2/file/copy", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "auto_rename": true, + "drive_id": d.DriveID, + "file_id": srcObj.GetID(), + "to_drive_id": d.DriveID, + "to_parent_file_id": dstDir.GetID(), + }) + }, nil) + return err +} + +func (d *AliCDE) Remove(ctx context.Context, obj model.Obj) error { + _, err, _ := d.request(d.ApiEndpoint+"/v2/recyclebin/trash", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "drive_id": d.DriveID, + "file_id": obj.GetID(), + "permanently": true, + }) + }, nil) + return err +} + +func (d *AliCDE) Put(ctx context.Context, dstDir model.Obj, streamer model.FileStreamer, up driver.UpdateProgress) error { + file := &stream.FileStream{ + Obj: streamer, + Reader: streamer, + Mimetype: streamer.GetMimetype(), + } + const DEFAULT int64 = 10485760 + count := int(math.Ceil(float64(streamer.GetSize()) / float64(DEFAULT))) + + partInfoList := make([]base.Json, 0, count) + for i := 1; i <= count; i++ { + partInfoList = append(partInfoList, base.Json{"part_number": i}) + } + reqBody := base.Json{ + "check_name_mode": "refuse", + "drive_id": d.DriveID, + "name": file.GetName(), + "parent_file_id": dstDir.GetID(), + "part_info_list": partInfoList, + "size": file.GetSize(), + "type": "file", + } + + var localFile *os.File + if fileStream, ok := file.Reader.(*stream.FileStream); ok { + localFile, _ = fileStream.Reader.(*os.File) + } + if d.RapidUpload { + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + _, err := utils.CopyWithBufferN(buf, file, 1024) + if err != nil { + return err + } + reqBody["pre_hash"] = utils.HashData(utils.SHA1, buf.Bytes()) + if localFile != nil { + if _, err := localFile.Seek(0, io.SeekStart); err != nil { + return err + } + } else { + // 把头部拼接回去 + file.Reader = struct { + io.Reader + io.Closer + }{ + Reader: io.MultiReader(buf, file), + Closer: file, + } + } + } else { + reqBody["content_hash_name"] = "none" + } + + var resp UploadResp + _, err, e := d.request(d.ApiEndpoint+"/v2/file/create", http.MethodPost, func(req *resty.Request) { + req.SetBody(reqBody) + }, &resp) + + if err != nil && e.Code != "PreHashMatched" { + return err + } + + if d.RapidUpload && e.Code == "PreHashMatched" { + delete(reqBody, "pre_hash") + h := sha1.New() + if localFile != nil { + if err = utils.CopyWithCtx(ctx, h, localFile, 0, nil); err != nil { + return err + } + if _, err = localFile.Seek(0, io.SeekStart); err != nil { + return err + } + } else { + tempFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*") + if err != nil { + return err + } + defer func() { + _ = tempFile.Close() + _ = os.Remove(tempFile.Name()) + }() + if err = utils.CopyWithCtx(ctx, io.MultiWriter(tempFile, h), file, 0, nil); err != nil { + return err + } + localFile = tempFile + } + reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil)) + reqBody["content_hash_name"] = "sha1" + + _, err, e := d.request(d.ApiEndpoint+"/v2/file/create", http.MethodPost, func(req *resty.Request) { + req.SetBody(reqBody) + }, &resp) + if err != nil && e.Code != "PreHashMatched" { + return err + } + if resp.RapidUpload { + return nil + } + // 秒传失败 + if _, err = localFile.Seek(0, io.SeekStart); err != nil { + return err + } + file.Reader = localFile + } + + rateLimited := driver.NewLimitedUploadStream(ctx, file) + for i, partInfo := range resp.PartInfoList { + if utils.IsCanceled(ctx) { + return ctx.Err() + } + url := partInfo.UploadUrl + if d.InternalUpload { + url = partInfo.InternalUploadUrl + } + req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, io.LimitReader(rateLimited, DEFAULT)) + if err != nil { + return err + } + res, err := base.HttpClient.Do(req) + if err != nil { + return err + } + _ = res.Body.Close() + if count > 0 { + up(float64(i) * 100 / float64(count)) + } + } + var resp2 base.Json + _, err, e = d.request(d.ApiEndpoint+"/v2/file/complete", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "drive_id": d.DriveID, + "file_id": resp.FileID, + "upload_id": resp.UploadID, + }) + }, &resp2) + if err != nil && e.Code != "PreHashMatched" { + return err + } + if resp2["file_id"] == resp.FileID { + return nil + } + return fmt.Errorf("%+v", resp2) +} + +func (d *AliCDE) GetDetails(ctx context.Context) (*model.StorageDetails, error) { + var resp DriveResp + _, err, _ := d.request(d.ApiEndpoint+"/v2/drive/get", http.MethodPost, func(req *resty.Request) { + req.SetContext(ctx) + req.SetBody(base.Json{"drive_id": d.DriveID}) + }, &resp) + if err != nil { + return nil, err + } + return &model.StorageDetails{ + DiskUsage: driver.DiskUsageFromUsedAndTotal(resp.UsedSize, resp.TotalSize), + }, nil +} + +var _ driver.Driver = (*AliCDE)(nil) diff --git a/drivers/aliyunfile/help.go b/drivers/aliyunfile/help.go new file mode 100644 index 000000000..303b5fe56 --- /dev/null +++ b/drivers/aliyunfile/help.go @@ -0,0 +1,66 @@ +package aliyunfile + +import ( + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "math/big" + + "github.com/dustinxie/ecc" +) + +func NewPrivateKey() (*ecdsa.PrivateKey, error) { + p256k1 := ecc.P256k1() + return ecdsa.GenerateKey(p256k1, rand.Reader) +} + +func NewPrivateKeyFromHex(hex_ string) (*ecdsa.PrivateKey, error) { + data, err := hex.DecodeString(hex_) + if err != nil { + return nil, err + } + return NewPrivateKeyFromBytes(data), nil + +} + +func NewPrivateKeyFromBytes(priv []byte) *ecdsa.PrivateKey { + p256k1 := ecc.P256k1() + x, y := p256k1.ScalarBaseMult(priv) + return &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: p256k1, + X: x, + Y: y, + }, + D: new(big.Int).SetBytes(priv), + } +} + +func PrivateKeyToHex(private *ecdsa.PrivateKey) string { + return hex.EncodeToString(PrivateKeyToBytes(private)) +} + +func PrivateKeyToBytes(private *ecdsa.PrivateKey) []byte { + return private.D.Bytes() +} + +func PublicKeyToHex(public *ecdsa.PublicKey) string { + return hex.EncodeToString(PublicKeyToBytes(public)) +} + +func PublicKeyToBytes(public *ecdsa.PublicKey) []byte { + x := public.X.Bytes() + if len(x) < 32 { + for i := 0; i < 32-len(x); i++ { + x = append([]byte{0}, x...) + } + } + + y := public.Y.Bytes() + if len(y) < 32 { + for i := 0; i < 32-len(y); i++ { + y = append([]byte{0}, y...) + } + } + return append(x, y...) +} diff --git a/drivers/aliyunfile/meta.go b/drivers/aliyunfile/meta.go new file mode 100644 index 000000000..9fc4c7a4c --- /dev/null +++ b/drivers/aliyunfile/meta.go @@ -0,0 +1,28 @@ +package aliyunfile + +import ( + "github.com/OpenListTeam/OpenList/v4/internal/driver" + "github.com/OpenListTeam/OpenList/v4/internal/op" +) + +type Addition struct { + driver.RootID + DomainID string `json:"domain_id" required:"true"` + DriveID string `json:"drive_id" required:"true"` + RefreshToken string `json:"refresh_token" required:"true"` + OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"` + OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"` + RapidUpload bool `json:"rapid_upload"` + InternalUpload bool `json:"internal_upload"` +} + +var config = driver.Config{ + Name: "Aliyunfile", + DefaultRoot: "root", +} + +func init() { + op.RegisterDriver(func() driver.Driver { + return &AliCDE{} + }) +} diff --git a/drivers/aliyunfile/types.go b/drivers/aliyunfile/types.go new file mode 100644 index 000000000..2f84f2058 --- /dev/null +++ b/drivers/aliyunfile/types.go @@ -0,0 +1,133 @@ +package aliyunfile + +import ( + "time" + + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/pkg/utils" +) + +type RespErr struct { + Code string `json:"code"` + Message string `json:"message"` +} + +type Files struct { + Items []File `json:"items"` + NextMarker string `json:"next_marker"` +} + +type File struct { + DriveID string `json:"drive_id"` + DomainID string `json:"domain_id"` + FileID string `json:"file_id"` + Name string `json:"name"` + Size int64 `json:"size"` + Type string `json:"type"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ParentFileID string `json:"parent_file_id"` + Thumbnail string `json:"thumbnail"` + Url string `json:"url"` + ContentHash string `json:"content_hash,omitempty"` + ContentHashName string `json:"content_hash_name,omitempty"` +} + +func fileToObj(f File) *model.ObjThumb { + ht, ok := utils.GetHashByName(f.ContentHashName) + var hashInfo utils.HashInfo + if ok { + hashInfo = utils.NewHashInfo(ht, f.ContentHash) + } else { + hashInfo = utils.NewHashInfo(nil, "") + } + return &model.ObjThumb{ + Object: model.Object{ + ID: f.FileID, + Name: f.Name, + Size: f.Size, + Modified: f.UpdatedAt, + IsFolder: f.Type == "folder", + HashInfo: hashInfo, + }, + Thumbnail: model.Thumbnail{Thumbnail: f.Thumbnail}, + } +} + +type UploadResp struct { + FileID string `json:"file_id"` + UploadID string `json:"upload_id"` + PartInfoList []struct { + UploadUrl string `json:"upload_url"` + InternalUploadUrl string `json:"internal_upload_url"` + } `json:"part_info_list"` + + RapidUpload bool `json:"rapid_upload"` +} + +type EndpointResp struct { + AuthEndpoint string `json:"auth_endpoint"` + APIEndpoint string `json:"api_endpoint"` + UIEndpoint string `json:"ui_endpoint"` + ParentDomainID string `json:"parent_domain_id"` + ClientID string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` + ProductType string `json:"product_type"` + DomainID string `json:"domain_id"` + IsVpc bool `json:"is_vpc"` + IsIntl bool `json:"is_intl"` +} + +type UserResp struct { + // DomainID string `json:"domain_id"` + // UserID string `json:"user_id"` + // Avatar string `json:"avatar"` + // CreatedAt int64 `json:"created_at"` + // UpdatedAt int64 `json:"updated_at"` + // Email string `json:"email"` + // NickName string `json:"nick_name"` + // Phone string `json:"phone"` + // PhoneRegion string `json:"phone_region"` + // Role string `json:"role"` + // Status string `json:"status"` + // UserName string `json:"user_name"` + // Description string `json:"description"` + DefaultDriveID string `json:"default_drive_id"` + // UserData struct { + // } `json:"user_data"` + // DenyChangePasswordBySelf bool `json:"deny_change_password_by_self"` + // NeedChangePasswordNextLogin bool `json:"need_change_password_next_login"` + // Creator string `json:"creator"` + // ExpiredAt int `json:"expired_at"` + // Permission interface{} `json:"permission"` + // DefaultLocation string `json:"default_location"` + // LastLoginTime int64 `json:"last_login_time"` + // MsgSetting struct { + // SendSettings []struct { + // Category string `json:"category"` + // SubCategory string `json:"sub_category"` + // Enable bool `json:"enable"` + // } `json:"send_settings"` + // } `json:"msg_setting"` +} + +type DriveResp struct { + DomainID string `json:"domain_id"` + DriveID string `json:"drive_id"` + DriveName string `json:"drive_name"` + Description string `json:"description"` + Creator string `json:"creator"` + Owner string `json:"owner"` + OwnerType string `json:"owner_type"` + DriveType string `json:"drive_type"` + Status string `json:"status"` + UsedSize uint64 `json:"used_size"` + TotalSize uint64 `json:"total_size"` + StoreID string `json:"store_id"` + RelativePath string `json:"relative_path"` + EncryptMode string `json:"encrypt_mode"` + EncryptDataAccess bool `json:"encrypt_data_access"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Category string `json:"category"` +} diff --git a/drivers/aliyunfile/util.go b/drivers/aliyunfile/util.go new file mode 100644 index 000000000..d1a762598 --- /dev/null +++ b/drivers/aliyunfile/util.go @@ -0,0 +1,144 @@ +package aliyunfile + +import ( + "errors" + "fmt" + "net/http" + + "github.com/OpenListTeam/OpenList/v4/drivers/base" + "github.com/OpenListTeam/OpenList/v4/internal/op" + "github.com/OpenListTeam/OpenList/v4/pkg/utils" + "github.com/go-resty/resty/v2" +) + +// do others that not defined in Driver interface + +func (d *AliCDE) refreshToken() error { + url := d.AuthEndpoint + "/v2/account/token" + var resp base.TokenResp + var e RespErr + _, err := base.RestyClient.R(). + //ForceContentType("application/json"). + SetBody(base.Json{"refresh_token": d.RefreshToken, "grant_type": "refresh_token"}). + SetResult(&resp). + SetError(&e). + Post(url) + if err != nil { + return err + } + if e.Code != "" { + return fmt.Errorf("failed to refresh token: %s", e.Message) + } + if resp.RefreshToken == "" { + return errors.New("failed to refresh token: refresh token is empty") + } + d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken + op.MustSaveDriverStorage(d) + return nil +} + +func (d *AliCDE) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) { + req := base.RestyClient.R() + req.SetHeaders(map[string]string{ + "Authorization": "Bearer\t" + d.AccessToken, + "content-type": "application/json", + "origin": d.UIEndpoint, + "Referer": d.UIEndpoint + "/", + }) + if callback != nil { + callback(req) + } else { + req.SetBody("{}") + } + if resp != nil { + req.SetResult(resp) + } + var e RespErr + req.SetError(&e) + res, err := req.Execute(method, url) + if err != nil { + return nil, err, e + } + if e.Code != "" { + switch e.Code { + case "AccessTokenInvalid": + err = d.refreshToken() + if err != nil { + return nil, err, e + } + default: + return nil, errors.New(e.Message), e + } + return d.request(url, method, callback, resp) + } else if res.IsError() { + return nil, errors.New("bad status code " + res.Status()), e + } + return res.Body(), nil, e +} + +func (d *AliCDE) getFiles(fileId string) ([]File, error) { + marker := "first" + res := make([]File, 0) + for marker != "" { + if marker == "first" { + marker = "" + } + var resp Files + data := base.Json{ + "domain_id": d.DomainID, + "drive_id": d.DriveID, + "fields": "*", + "image_thumbnail_process": "image/resize,w_400/format,jpeg", + "image_url_process": "image/resize,w_1920/format,jpeg", + "limit": 200, + "marker": marker, + "order_by": d.OrderBy, + "order_direction": d.OrderDirection, + "parent_file_id": fileId, + "video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300", + "url_expire_sec": 7200, + } + _, err, _ := d.request(d.ApiEndpoint+"/v2/file/list", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, &resp) + + if err != nil { + return nil, err + } + marker = resp.NextMarker + res = append(res, resp.Items...) + } + return res, nil +} + +func (d *AliCDE) batch(srcId, dstId string, url string) error { + res, err, _ := d.request(d.ApiEndpoint+"/v2/batch", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "requests": []base.Json{ + { + "headers": base.Json{ + "Content-Type": "application/json", + }, + "method": "POST", + "id": srcId, + "body": base.Json{ + "drive_id": d.DriveID, + "file_id": srcId, + "to_drive_id": d.DriveID, + "to_parent_file_id": dstId, + }, + "url": url, + }, + }, + "resource": "file", + }) + }, nil) + if err != nil { + return err + } + status := utils.Json.Get(res, "responses", 0, "status").ToInt() + if status < 400 && status >= 100 { + return nil + } + return errors.New(string(res)) +} diff --git a/drivers/all.go b/drivers/all.go index 15fbf2b96..359d3c4cc 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -17,6 +17,7 @@ import ( _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive" _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive_open" _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive_share" + _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyunfile" _ "github.com/OpenListTeam/OpenList/v4/drivers/azure_blob" _ "github.com/OpenListTeam/OpenList/v4/drivers/baidu_netdisk" _ "github.com/OpenListTeam/OpenList/v4/drivers/baidu_photo" From 63b436d6fce2c4f514f327145b0202761819eb83 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 24 Dec 2025 16:53:51 +0800 Subject: [PATCH 2/7] fix upload Signed-off-by: MadDogOwner --- drivers/aliyunfile/driver.go | 4 ++-- drivers/aliyunfile/meta.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/aliyunfile/driver.go b/drivers/aliyunfile/driver.go index a5c90280d..2fe315457 100644 --- a/drivers/aliyunfile/driver.go +++ b/drivers/aliyunfile/driver.go @@ -228,8 +228,8 @@ func (d *AliCDE) Put(ctx context.Context, dstDir model.Obj, streamer model.FileS io.Reader io.Closer }{ - Reader: io.MultiReader(buf, file), - Closer: file, + Reader: io.MultiReader(buf, streamer), + Closer: streamer, } } } else { diff --git a/drivers/aliyunfile/meta.go b/drivers/aliyunfile/meta.go index 9fc4c7a4c..0911e9e44 100644 --- a/drivers/aliyunfile/meta.go +++ b/drivers/aliyunfile/meta.go @@ -17,8 +17,9 @@ type Addition struct { } var config = driver.Config{ - Name: "Aliyunfile", - DefaultRoot: "root", + Name: "Aliyunfile", + DefaultRoot: "root", + NoOverwriteUpload: true, } func init() { From 35499cca600d4ed8d77ceeca1fcd2f6bf1259b70 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 24 Dec 2025 17:01:57 +0800 Subject: [PATCH 3/7] remove user resp Signed-off-by: MadDogOwner --- drivers/aliyunfile/driver.go | 11 ----------- drivers/aliyunfile/types.go | 33 --------------------------------- 2 files changed, 44 deletions(-) diff --git a/drivers/aliyunfile/driver.go b/drivers/aliyunfile/driver.go index 2fe315457..23ef91ed4 100644 --- a/drivers/aliyunfile/driver.go +++ b/drivers/aliyunfile/driver.go @@ -16,7 +16,6 @@ import ( "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/driver" "github.com/OpenListTeam/OpenList/v4/internal/model" - "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/internal/stream" "github.com/OpenListTeam/OpenList/v4/pkg/cron" "github.com/OpenListTeam/OpenList/v4/pkg/utils" @@ -59,16 +58,6 @@ func (d *AliCDE) Init(ctx context.Context) error { if err != nil { return err } - // get driver id - if d.DriveID == "" { - var userp UserResp - _, err, _ = d.request(d.ApiEndpoint+"/v2/user/get", http.MethodPost, nil, &userp) - if err != nil { - return err - } - d.DriveID = userp.DefaultDriveID - op.MustSaveDriverStorage(d) - } d.cron = cron.NewCron(time.Hour * 2) d.cron.Do(func() { err := d.refreshToken() diff --git a/drivers/aliyunfile/types.go b/drivers/aliyunfile/types.go index 2f84f2058..99d734e9a 100644 --- a/drivers/aliyunfile/types.go +++ b/drivers/aliyunfile/types.go @@ -78,39 +78,6 @@ type EndpointResp struct { IsIntl bool `json:"is_intl"` } -type UserResp struct { - // DomainID string `json:"domain_id"` - // UserID string `json:"user_id"` - // Avatar string `json:"avatar"` - // CreatedAt int64 `json:"created_at"` - // UpdatedAt int64 `json:"updated_at"` - // Email string `json:"email"` - // NickName string `json:"nick_name"` - // Phone string `json:"phone"` - // PhoneRegion string `json:"phone_region"` - // Role string `json:"role"` - // Status string `json:"status"` - // UserName string `json:"user_name"` - // Description string `json:"description"` - DefaultDriveID string `json:"default_drive_id"` - // UserData struct { - // } `json:"user_data"` - // DenyChangePasswordBySelf bool `json:"deny_change_password_by_self"` - // NeedChangePasswordNextLogin bool `json:"need_change_password_next_login"` - // Creator string `json:"creator"` - // ExpiredAt int `json:"expired_at"` - // Permission interface{} `json:"permission"` - // DefaultLocation string `json:"default_location"` - // LastLoginTime int64 `json:"last_login_time"` - // MsgSetting struct { - // SendSettings []struct { - // Category string `json:"category"` - // SubCategory string `json:"sub_category"` - // Enable bool `json:"enable"` - // } `json:"send_settings"` - // } `json:"msg_setting"` -} - type DriveResp struct { DomainID string `json:"domain_id"` DriveID string `json:"drive_id"` From 25a3b40d58c8166dbf70e7e0eaff4294e750e6a5 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Wed, 24 Dec 2025 17:07:59 +0800 Subject: [PATCH 4/7] rename to aliyun pds Signed-off-by: MadDogOwner --- drivers/{aliyunfile => aliyun_pds}/driver.go | 32 ++++++++++---------- drivers/{aliyunfile => aliyun_pds}/help.go | 2 +- drivers/{aliyunfile => aliyun_pds}/meta.go | 6 ++-- drivers/{aliyunfile => aliyun_pds}/types.go | 2 +- drivers/{aliyunfile => aliyun_pds}/util.go | 10 +++--- drivers/all.go | 2 +- 6 files changed, 27 insertions(+), 27 deletions(-) rename drivers/{aliyunfile => aliyun_pds}/driver.go (90%) rename drivers/{aliyunfile => aliyun_pds}/help.go (98%) rename drivers/{aliyunfile => aliyun_pds}/meta.go (91%) rename drivers/{aliyunfile => aliyun_pds}/types.go (99%) rename drivers/{aliyunfile => aliyun_pds}/util.go (93%) diff --git a/drivers/aliyunfile/driver.go b/drivers/aliyun_pds/driver.go similarity index 90% rename from drivers/aliyunfile/driver.go rename to drivers/aliyun_pds/driver.go index 23ef91ed4..1507dbd8f 100644 --- a/drivers/aliyunfile/driver.go +++ b/drivers/aliyun_pds/driver.go @@ -1,4 +1,4 @@ -package aliyunfile +package aliyun_pds import ( "bytes" @@ -23,7 +23,7 @@ import ( log "github.com/sirupsen/logrus" ) -type AliCDE struct { +type AliPDS struct { model.Storage Addition AccessToken string @@ -33,15 +33,15 @@ type AliCDE struct { cron *cron.Cron } -func (d *AliCDE) Config() driver.Config { +func (d *AliPDS) Config() driver.Config { return config } -func (d *AliCDE) GetAddition() driver.Additional { +func (d *AliPDS) GetAddition() driver.Additional { return &d.Addition } -func (d *AliCDE) Init(ctx context.Context) error { +func (d *AliPDS) Init(ctx context.Context) error { // get entrypoint var endp EndpointResp _, err, _ := d.request("https://web-sv.aliyunpds.com/endpoint/get_endpoints", http.MethodPost, func(req *resty.Request) { @@ -68,14 +68,14 @@ func (d *AliCDE) Init(ctx context.Context) error { return nil } -func (d *AliCDE) Drop(ctx context.Context) error { +func (d *AliPDS) Drop(ctx context.Context) error { if d.cron != nil { d.cron.Stop() } return nil } -func (d *AliCDE) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { +func (d *AliPDS) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { files, err := d.getFiles(dir.GetID()) if err != nil { return nil, err @@ -85,7 +85,7 @@ func (d *AliCDE) List(ctx context.Context, dir model.Obj, args model.ListArgs) ( }) } -func (d *AliCDE) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { +func (d *AliPDS) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { data := base.Json{ "drive_id": d.DriveID, "file_id": file.GetID(), @@ -110,7 +110,7 @@ func (d *AliCDE) Link(ctx context.Context, file model.Obj, args model.LinkArgs) }, nil } -func (d *AliCDE) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { +func (d *AliPDS) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { _, err, _ := d.request(d.ApiEndpoint+"/v2/file/create", http.MethodPost, func(req *resty.Request) { req.SetBody(base.Json{ "check_name_mode": "refuse", @@ -124,7 +124,7 @@ func (d *AliCDE) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin return err } -func (d *AliCDE) Move(ctx context.Context, srcObj, dstDir model.Obj) error { +func (d *AliPDS) Move(ctx context.Context, srcObj, dstDir model.Obj) error { _, err, _ := d.request(d.ApiEndpoint+"/v2/file/move", http.MethodPost, func(req *resty.Request) { req.SetBody(base.Json{ "auto_rename": true, @@ -137,7 +137,7 @@ func (d *AliCDE) Move(ctx context.Context, srcObj, dstDir model.Obj) error { return err } -func (d *AliCDE) Rename(ctx context.Context, srcObj model.Obj, newName string) error { +func (d *AliPDS) Rename(ctx context.Context, srcObj model.Obj, newName string) error { _, err, _ := d.request(d.ApiEndpoint+"/v2/file/update", http.MethodPost, func(req *resty.Request) { req.SetBody(base.Json{ "check_name_mode": "refuse", @@ -149,7 +149,7 @@ func (d *AliCDE) Rename(ctx context.Context, srcObj model.Obj, newName string) e return err } -func (d *AliCDE) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { +func (d *AliPDS) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { _, err, _ := d.request(d.ApiEndpoint+"/v2/file/copy", http.MethodPost, func(req *resty.Request) { req.SetBody(base.Json{ "auto_rename": true, @@ -162,7 +162,7 @@ func (d *AliCDE) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { return err } -func (d *AliCDE) Remove(ctx context.Context, obj model.Obj) error { +func (d *AliPDS) Remove(ctx context.Context, obj model.Obj) error { _, err, _ := d.request(d.ApiEndpoint+"/v2/recyclebin/trash", http.MethodPost, func(req *resty.Request) { req.SetBody(base.Json{ "drive_id": d.DriveID, @@ -173,7 +173,7 @@ func (d *AliCDE) Remove(ctx context.Context, obj model.Obj) error { return err } -func (d *AliCDE) Put(ctx context.Context, dstDir model.Obj, streamer model.FileStreamer, up driver.UpdateProgress) error { +func (d *AliPDS) Put(ctx context.Context, dstDir model.Obj, streamer model.FileStreamer, up driver.UpdateProgress) error { file := &stream.FileStream{ Obj: streamer, Reader: streamer, @@ -316,7 +316,7 @@ func (d *AliCDE) Put(ctx context.Context, dstDir model.Obj, streamer model.FileS return fmt.Errorf("%+v", resp2) } -func (d *AliCDE) GetDetails(ctx context.Context) (*model.StorageDetails, error) { +func (d *AliPDS) GetDetails(ctx context.Context) (*model.StorageDetails, error) { var resp DriveResp _, err, _ := d.request(d.ApiEndpoint+"/v2/drive/get", http.MethodPost, func(req *resty.Request) { req.SetContext(ctx) @@ -330,4 +330,4 @@ func (d *AliCDE) GetDetails(ctx context.Context) (*model.StorageDetails, error) }, nil } -var _ driver.Driver = (*AliCDE)(nil) +var _ driver.Driver = (*AliPDS)(nil) diff --git a/drivers/aliyunfile/help.go b/drivers/aliyun_pds/help.go similarity index 98% rename from drivers/aliyunfile/help.go rename to drivers/aliyun_pds/help.go index 303b5fe56..434ff5ca8 100644 --- a/drivers/aliyunfile/help.go +++ b/drivers/aliyun_pds/help.go @@ -1,4 +1,4 @@ -package aliyunfile +package aliyun_pds import ( "crypto/ecdsa" diff --git a/drivers/aliyunfile/meta.go b/drivers/aliyun_pds/meta.go similarity index 91% rename from drivers/aliyunfile/meta.go rename to drivers/aliyun_pds/meta.go index 0911e9e44..14fc29d19 100644 --- a/drivers/aliyunfile/meta.go +++ b/drivers/aliyun_pds/meta.go @@ -1,4 +1,4 @@ -package aliyunfile +package aliyun_pds import ( "github.com/OpenListTeam/OpenList/v4/internal/driver" @@ -17,13 +17,13 @@ type Addition struct { } var config = driver.Config{ - Name: "Aliyunfile", + Name: "Aliyun PDS", DefaultRoot: "root", NoOverwriteUpload: true, } func init() { op.RegisterDriver(func() driver.Driver { - return &AliCDE{} + return &AliPDS{} }) } diff --git a/drivers/aliyunfile/types.go b/drivers/aliyun_pds/types.go similarity index 99% rename from drivers/aliyunfile/types.go rename to drivers/aliyun_pds/types.go index 99d734e9a..b6c5a09a4 100644 --- a/drivers/aliyunfile/types.go +++ b/drivers/aliyun_pds/types.go @@ -1,4 +1,4 @@ -package aliyunfile +package aliyun_pds import ( "time" diff --git a/drivers/aliyunfile/util.go b/drivers/aliyun_pds/util.go similarity index 93% rename from drivers/aliyunfile/util.go rename to drivers/aliyun_pds/util.go index d1a762598..c9375c2c8 100644 --- a/drivers/aliyunfile/util.go +++ b/drivers/aliyun_pds/util.go @@ -1,4 +1,4 @@ -package aliyunfile +package aliyun_pds import ( "errors" @@ -13,7 +13,7 @@ import ( // do others that not defined in Driver interface -func (d *AliCDE) refreshToken() error { +func (d *AliPDS) refreshToken() error { url := d.AuthEndpoint + "/v2/account/token" var resp base.TokenResp var e RespErr @@ -37,7 +37,7 @@ func (d *AliCDE) refreshToken() error { return nil } -func (d *AliCDE) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) { +func (d *AliPDS) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) { req := base.RestyClient.R() req.SetHeaders(map[string]string{ "Authorization": "Bearer\t" + d.AccessToken, @@ -76,7 +76,7 @@ func (d *AliCDE) request(url, method string, callback base.ReqCallback, resp int return res.Body(), nil, e } -func (d *AliCDE) getFiles(fileId string) ([]File, error) { +func (d *AliPDS) getFiles(fileId string) ([]File, error) { marker := "first" res := make([]File, 0) for marker != "" { @@ -111,7 +111,7 @@ func (d *AliCDE) getFiles(fileId string) ([]File, error) { return res, nil } -func (d *AliCDE) batch(srcId, dstId string, url string) error { +func (d *AliPDS) batch(srcId, dstId string, url string) error { res, err, _ := d.request(d.ApiEndpoint+"/v2/batch", http.MethodPost, func(req *resty.Request) { req.SetBody(base.Json{ "requests": []base.Json{ diff --git a/drivers/all.go b/drivers/all.go index 359d3c4cc..d1bf84ec4 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -14,10 +14,10 @@ import ( _ "github.com/OpenListTeam/OpenList/v4/drivers/189pc" _ "github.com/OpenListTeam/OpenList/v4/drivers/alias" _ "github.com/OpenListTeam/OpenList/v4/drivers/alist_v3" + _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyun_pds" _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive" _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive_open" _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyundrive_share" - _ "github.com/OpenListTeam/OpenList/v4/drivers/aliyunfile" _ "github.com/OpenListTeam/OpenList/v4/drivers/azure_blob" _ "github.com/OpenListTeam/OpenList/v4/drivers/baidu_netdisk" _ "github.com/OpenListTeam/OpenList/v4/drivers/baidu_photo" From 25e2b97d17c68a4ca80a163e5e2198856609c7a3 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Thu, 8 Jan 2026 10:57:59 +0800 Subject: [PATCH 5/7] follow GetDetails type change Signed-off-by: MadDogOwner --- drivers/aliyun_pds/driver.go | 5 ++++- drivers/aliyun_pds/types.go | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/aliyun_pds/driver.go b/drivers/aliyun_pds/driver.go index 1507dbd8f..7788d150e 100644 --- a/drivers/aliyun_pds/driver.go +++ b/drivers/aliyun_pds/driver.go @@ -326,7 +326,10 @@ func (d *AliPDS) GetDetails(ctx context.Context) (*model.StorageDetails, error) return nil, err } return &model.StorageDetails{ - DiskUsage: driver.DiskUsageFromUsedAndTotal(resp.UsedSize, resp.TotalSize), + DiskUsage: model.DiskUsage{ + TotalSpace: resp.TotalSize, + UsedSpace: resp.UsedSize, + }, }, nil } diff --git a/drivers/aliyun_pds/types.go b/drivers/aliyun_pds/types.go index b6c5a09a4..7a7d92dfd 100644 --- a/drivers/aliyun_pds/types.go +++ b/drivers/aliyun_pds/types.go @@ -88,8 +88,8 @@ type DriveResp struct { OwnerType string `json:"owner_type"` DriveType string `json:"drive_type"` Status string `json:"status"` - UsedSize uint64 `json:"used_size"` - TotalSize uint64 `json:"total_size"` + UsedSize int64 `json:"used_size"` + TotalSize int64 `json:"total_size"` StoreID string `json:"store_id"` RelativePath string `json:"relative_path"` EncryptMode string `json:"encrypt_mode"` From 8e90dd711078c874b54ff0dc5c76535eacbc0ba6 Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Thu, 8 Jan 2026 11:39:06 +0800 Subject: [PATCH 6/7] remove unused code Signed-off-by: MadDogOwner --- drivers/aliyun_pds/driver.go | 3 -- drivers/aliyun_pds/help.go | 66 ------------------------------------ drivers/aliyun_pds/meta.go | 1 - 3 files changed, 70 deletions(-) delete mode 100644 drivers/aliyun_pds/help.go diff --git a/drivers/aliyun_pds/driver.go b/drivers/aliyun_pds/driver.go index 7788d150e..84e0d2d5d 100644 --- a/drivers/aliyun_pds/driver.go +++ b/drivers/aliyun_pds/driver.go @@ -283,9 +283,6 @@ func (d *AliPDS) Put(ctx context.Context, dstDir model.Obj, streamer model.FileS return ctx.Err() } url := partInfo.UploadUrl - if d.InternalUpload { - url = partInfo.InternalUploadUrl - } req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, io.LimitReader(rateLimited, DEFAULT)) if err != nil { return err diff --git a/drivers/aliyun_pds/help.go b/drivers/aliyun_pds/help.go deleted file mode 100644 index 434ff5ca8..000000000 --- a/drivers/aliyun_pds/help.go +++ /dev/null @@ -1,66 +0,0 @@ -package aliyun_pds - -import ( - "crypto/ecdsa" - "crypto/rand" - "encoding/hex" - "math/big" - - "github.com/dustinxie/ecc" -) - -func NewPrivateKey() (*ecdsa.PrivateKey, error) { - p256k1 := ecc.P256k1() - return ecdsa.GenerateKey(p256k1, rand.Reader) -} - -func NewPrivateKeyFromHex(hex_ string) (*ecdsa.PrivateKey, error) { - data, err := hex.DecodeString(hex_) - if err != nil { - return nil, err - } - return NewPrivateKeyFromBytes(data), nil - -} - -func NewPrivateKeyFromBytes(priv []byte) *ecdsa.PrivateKey { - p256k1 := ecc.P256k1() - x, y := p256k1.ScalarBaseMult(priv) - return &ecdsa.PrivateKey{ - PublicKey: ecdsa.PublicKey{ - Curve: p256k1, - X: x, - Y: y, - }, - D: new(big.Int).SetBytes(priv), - } -} - -func PrivateKeyToHex(private *ecdsa.PrivateKey) string { - return hex.EncodeToString(PrivateKeyToBytes(private)) -} - -func PrivateKeyToBytes(private *ecdsa.PrivateKey) []byte { - return private.D.Bytes() -} - -func PublicKeyToHex(public *ecdsa.PublicKey) string { - return hex.EncodeToString(PublicKeyToBytes(public)) -} - -func PublicKeyToBytes(public *ecdsa.PublicKey) []byte { - x := public.X.Bytes() - if len(x) < 32 { - for i := 0; i < 32-len(x); i++ { - x = append([]byte{0}, x...) - } - } - - y := public.Y.Bytes() - if len(y) < 32 { - for i := 0; i < 32-len(y); i++ { - y = append([]byte{0}, y...) - } - } - return append(x, y...) -} diff --git a/drivers/aliyun_pds/meta.go b/drivers/aliyun_pds/meta.go index 14fc29d19..045b35e42 100644 --- a/drivers/aliyun_pds/meta.go +++ b/drivers/aliyun_pds/meta.go @@ -13,7 +13,6 @@ type Addition struct { OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"` OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"` RapidUpload bool `json:"rapid_upload"` - InternalUpload bool `json:"internal_upload"` } var config = driver.Config{ From ffb429da2b319edbfe99336fdc27af1ae883be3b Mon Sep 17 00:00:00 2001 From: MadDogOwner Date: Thu, 8 Jan 2026 16:34:47 +0800 Subject: [PATCH 7/7] keep hashInfo nil when invalid hash Signed-off-by: MadDogOwner --- drivers/aliyun_pds/types.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/aliyun_pds/types.go b/drivers/aliyun_pds/types.go index 7a7d92dfd..a4835dd9f 100644 --- a/drivers/aliyun_pds/types.go +++ b/drivers/aliyun_pds/types.go @@ -34,12 +34,9 @@ type File struct { } func fileToObj(f File) *model.ObjThumb { - ht, ok := utils.GetHashByName(f.ContentHashName) var hashInfo utils.HashInfo - if ok { + if ht, ok := utils.GetHashByName(f.ContentHashName); ok { hashInfo = utils.NewHashInfo(ht, f.ContentHash) - } else { - hashInfo = utils.NewHashInfo(nil, "") } return &model.ObjThumb{ Object: model.Object{