mirror of
https://github.com/AlistGo/alist.git
synced 2025-06-17 01:42:32 +08:00

Some checks failed
auto_lang / auto generate lang.json (1.21, ubuntu-latest) (push) Has been cancelled
build / Build (ubuntu-latest, android-arm64) (push) Has been cancelled
beta release / Beta Release Changelog (1.21, ubuntu-latest) (push) Has been cancelled
build / Build (ubuntu-latest, darwin-amd64) (push) Has been cancelled
build / Build (ubuntu-latest, darwin-arm64) (push) Has been cancelled
build / Build (ubuntu-latest, linux-amd64-musl) (push) Has been cancelled
build / Build (ubuntu-latest, linux-arm64-musl) (push) Has been cancelled
build / Build (ubuntu-latest, windows-amd64) (push) Has been cancelled
build / Build (ubuntu-latest, windows-arm64) (push) Has been cancelled
release_docker / Build Binaries for Docker Release (push) Has been cancelled
beta release / Beta Release (md5, !(*musl*|*windows-arm64*|*android*|*freebsd*)) (push) Has been cancelled
beta release / Beta Release (md5-android, android-*) (push) Has been cancelled
beta release / Beta Release (md5-freebsd, freebsd-*) (push) Has been cancelled
beta release / Beta Release (md5-linux-musl, linux-!(arm*)-musl*) (push) Has been cancelled
beta release / Beta Release (md5-linux-musl-arm, linux-arm*-musl*) (push) Has been cancelled
beta release / Beta Release (md5-windows-arm64, windows-arm64) (push) Has been cancelled
beta release / Beta Release Desktop (push) Has been cancelled
release_docker / Release Docker image (INSTALL_FFMPEG=true
INSTALL_ARIA2=true
, aio, suffix=-aio,onlatest=true) (push) Has been cancelled
release_docker / Release Docker image (, latest, ) (push) Has been cancelled
release_docker / Release Docker image (INSTALL_ARIA2=true, aria2, suffix=-aria2,onlatest=true) (push) Has been cancelled
release_docker / Release Docker image (INSTALL_FFMPEG=true, ffmpeg, suffix=-ffmpeg,onlatest=true) (push) Has been cancelled
Close need info / close-need-info (push) Has been cancelled
Close inactive / close-inactive (push) Has been cancelled
* feat(cloudreve_v4): add Cloudreve V4 driver implementation * fix(cloudreve_v4): update request handling to prevent token refresh loop * feat(onedrive): implement retry logic for upload failures * feat(cloudreve): implement retry logic for upload failures * feat(cloudreve_v4): support cloud sorting * fix(cloudreve_v4): improve token handling in Init method * feat(cloudreve_v4): support share * feat(cloudreve): support reference * feat(cloudreve_v4): support version upload * fix(cloudreve_v4): add SetBody in upLocal * fix(cloudreve_v4): update URL structure in Link and FileUrlResp
306 lines
8.2 KiB
Go
306 lines
8.2 KiB
Go
package cloudreve_v4
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
"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/internal/op"
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
|
"github.com/go-resty/resty/v2"
|
|
)
|
|
|
|
type CloudreveV4 struct {
|
|
model.Storage
|
|
Addition
|
|
ref *CloudreveV4
|
|
}
|
|
|
|
func (d *CloudreveV4) Config() driver.Config {
|
|
if d.ref != nil {
|
|
return d.ref.Config()
|
|
}
|
|
if d.EnableVersionUpload {
|
|
config.NoOverwriteUpload = false
|
|
}
|
|
return config
|
|
}
|
|
|
|
func (d *CloudreveV4) GetAddition() driver.Additional {
|
|
return &d.Addition
|
|
}
|
|
|
|
func (d *CloudreveV4) Init(ctx context.Context) error {
|
|
// removing trailing slash
|
|
d.Address = strings.TrimSuffix(d.Address, "/")
|
|
op.MustSaveDriverStorage(d)
|
|
if d.ref != nil {
|
|
return nil
|
|
}
|
|
if d.AccessToken == "" && d.RefreshToken != "" {
|
|
return d.refreshToken()
|
|
}
|
|
if d.Username != "" {
|
|
return d.login()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *CloudreveV4) InitReference(storage driver.Driver) error {
|
|
refStorage, ok := storage.(*CloudreveV4)
|
|
if ok {
|
|
d.ref = refStorage
|
|
return nil
|
|
}
|
|
return errs.NotSupport
|
|
}
|
|
|
|
func (d *CloudreveV4) Drop(ctx context.Context) error {
|
|
d.ref = nil
|
|
return nil
|
|
}
|
|
|
|
func (d *CloudreveV4) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
|
const pageSize int = 100
|
|
var f []File
|
|
var r FileResp
|
|
params := map[string]string{
|
|
"page_size": strconv.Itoa(pageSize),
|
|
"uri": dir.GetPath(),
|
|
"order_by": d.OrderBy,
|
|
"order_direction": d.OrderDirection,
|
|
"page": "0",
|
|
}
|
|
|
|
for {
|
|
err := d.request(http.MethodGet, "/file", func(req *resty.Request) {
|
|
req.SetQueryParams(params)
|
|
}, &r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f = append(f, r.Files...)
|
|
if r.Pagination.NextToken == "" || len(r.Files) < pageSize {
|
|
break
|
|
}
|
|
params["next_page_token"] = r.Pagination.NextToken
|
|
}
|
|
|
|
return utils.SliceConvert(f, func(src File) (model.Obj, error) {
|
|
if d.EnableFolderSize && src.Type == 1 {
|
|
var ds FolderSummaryResp
|
|
err := d.request(http.MethodGet, "/file/info", func(req *resty.Request) {
|
|
req.SetQueryParam("uri", src.Path)
|
|
req.SetQueryParam("folder_summary", "true")
|
|
}, &ds)
|
|
if err == nil && ds.FolderSummary.Size > 0 {
|
|
src.Size = ds.FolderSummary.Size
|
|
}
|
|
}
|
|
var thumb model.Thumbnail
|
|
if d.EnableThumb && src.Type == 0 {
|
|
var t FileThumbResp
|
|
err := d.request(http.MethodGet, "/file/thumb", func(req *resty.Request) {
|
|
req.SetQueryParam("uri", src.Path)
|
|
}, &t)
|
|
if err == nil && t.URL != "" {
|
|
thumb = model.Thumbnail{
|
|
Thumbnail: t.URL,
|
|
}
|
|
}
|
|
}
|
|
return &model.ObjThumb{
|
|
Object: model.Object{
|
|
ID: src.ID,
|
|
Path: src.Path,
|
|
Name: src.Name,
|
|
Size: src.Size,
|
|
Modified: src.UpdatedAt,
|
|
Ctime: src.CreatedAt,
|
|
IsFolder: src.Type == 1,
|
|
},
|
|
Thumbnail: thumb,
|
|
}, nil
|
|
})
|
|
}
|
|
|
|
func (d *CloudreveV4) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
var url FileUrlResp
|
|
err := d.request(http.MethodPost, "/file/url", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"uris": []string{file.GetPath()},
|
|
"download": true,
|
|
})
|
|
}, &url)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(url.Urls) == 0 {
|
|
return nil, errors.New("server returns no url")
|
|
}
|
|
exp := time.Until(url.Expires)
|
|
return &model.Link{
|
|
URL: url.Urls[0].URL,
|
|
Expiration: &exp,
|
|
}, nil
|
|
}
|
|
|
|
func (d *CloudreveV4) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
|
return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"type": "folder",
|
|
"uri": parentDir.GetPath() + "/" + dirName,
|
|
"error_on_conflict": true,
|
|
})
|
|
}, nil)
|
|
}
|
|
|
|
func (d *CloudreveV4) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
return d.request(http.MethodPost, "/file/move", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"uris": []string{srcObj.GetPath()},
|
|
"dst": dstDir.GetPath(),
|
|
"copy": false,
|
|
})
|
|
}, nil)
|
|
}
|
|
|
|
func (d *CloudreveV4) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
|
return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"new_name": newName,
|
|
"uri": srcObj.GetPath(),
|
|
})
|
|
}, nil)
|
|
|
|
}
|
|
|
|
func (d *CloudreveV4) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|
return d.request(http.MethodPost, "/file/move", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"uris": []string{srcObj.GetPath()},
|
|
"dst": dstDir.GetPath(),
|
|
"copy": true,
|
|
})
|
|
}, nil)
|
|
}
|
|
|
|
func (d *CloudreveV4) Remove(ctx context.Context, obj model.Obj) error {
|
|
return d.request(http.MethodDelete, "/file", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"uris": []string{obj.GetPath()},
|
|
"unlink": false,
|
|
"skip_soft_delete": true,
|
|
})
|
|
}, nil)
|
|
}
|
|
|
|
func (d *CloudreveV4) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
|
|
if file.GetSize() == 0 {
|
|
// 空文件使用新建文件方法,避免上传卡锁
|
|
return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"type": "file",
|
|
"uri": dstDir.GetPath() + "/" + file.GetName(),
|
|
"error_on_conflict": true,
|
|
})
|
|
}, nil)
|
|
}
|
|
var p StoragePolicy
|
|
var r FileResp
|
|
var u FileUploadResp
|
|
var err error
|
|
params := map[string]string{
|
|
"page_size": "10",
|
|
"uri": dstDir.GetPath(),
|
|
"order_by": "created_at",
|
|
"order_direction": "asc",
|
|
"page": "0",
|
|
}
|
|
err = d.request(http.MethodGet, "/file", func(req *resty.Request) {
|
|
req.SetQueryParams(params)
|
|
}, &r)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p = r.StoragePolicy
|
|
body := base.Json{
|
|
"uri": dstDir.GetPath() + "/" + file.GetName(),
|
|
"size": file.GetSize(),
|
|
"policy_id": p.ID,
|
|
"last_modified": file.ModTime().UnixMilli(),
|
|
"mime_type": "",
|
|
}
|
|
if d.EnableVersionUpload {
|
|
body["entity_type"] = "version"
|
|
}
|
|
err = d.request(http.MethodPut, "/file/upload", func(req *resty.Request) {
|
|
req.SetBody(body)
|
|
}, &u)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if u.StoragePolicy.Relay {
|
|
err = d.upLocal(ctx, file, u, up)
|
|
} else {
|
|
switch u.StoragePolicy.Type {
|
|
case "local":
|
|
err = d.upLocal(ctx, file, u, up)
|
|
case "remote":
|
|
err = d.upRemote(ctx, file, u, up)
|
|
case "onedrive":
|
|
err = d.upOneDrive(ctx, file, u, up)
|
|
case "s3":
|
|
err = d.upS3(ctx, file, u, up)
|
|
default:
|
|
return errs.NotImplement
|
|
}
|
|
}
|
|
if err != nil {
|
|
// 删除失败的会话
|
|
_ = d.request(http.MethodDelete, "/file/upload", func(req *resty.Request) {
|
|
req.SetBody(base.Json{
|
|
"id": u.SessionID,
|
|
"uri": u.URI,
|
|
})
|
|
}, nil)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *CloudreveV4) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
|
// TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
func (d *CloudreveV4) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
|
|
// TODO list args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
func (d *CloudreveV4) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
|
|
// TODO return link of file args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
func (d *CloudreveV4) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) {
|
|
// TODO extract args.InnerPath path in the archive srcObj to the dstDir location, optional
|
|
// a folder with the same name as the archive file needs to be created to store the extracted results if args.PutIntoNewDir
|
|
// return errs.NotImplement to use an internal archive tool
|
|
return nil, errs.NotImplement
|
|
}
|
|
|
|
//func (d *CloudreveV4) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
|
// return nil, errs.NotSupport
|
|
//}
|
|
|
|
var _ driver.Driver = (*CloudreveV4)(nil)
|