mirror of https://github.com/Xhofe/alist
fix(189pc): transfer rename (#7958)
* fix(189pc): transfer rename * fix: OverwriteUpload * fix: change search method * fix * fixpull/7976/merge
parent
36b4204623
commit
399336b33c
|
@ -1,8 +1,8 @@
|
|||
package _189pc
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Cloud189PC struct {
|
||||
|
@ -29,7 +30,7 @@ type Cloud189PC struct {
|
|||
|
||||
uploadThread int
|
||||
|
||||
familyTransferFolder *ring.Ring
|
||||
familyTransferFolder *Cloud189Folder
|
||||
cleanFamilyTransferFile func()
|
||||
|
||||
storageConfig driver.Config
|
||||
|
@ -48,9 +49,18 @@ func (y *Cloud189PC) GetAddition() driver.Additional {
|
|||
}
|
||||
|
||||
func (y *Cloud189PC) Init(ctx context.Context) (err error) {
|
||||
// 兼容旧上传接口
|
||||
y.storageConfig.NoOverwriteUpload = y.isFamily() && (y.Addition.RapidUpload || y.Addition.UploadMethod == "old")
|
||||
|
||||
y.storageConfig = config
|
||||
if y.isFamily() {
|
||||
// 兼容旧上传接口
|
||||
if y.Addition.RapidUpload || y.Addition.UploadMethod == "old" {
|
||||
y.storageConfig.NoOverwriteUpload = true
|
||||
}
|
||||
} else {
|
||||
// 家庭云转存,不支持覆盖上传
|
||||
if y.Addition.FamilyTransfer {
|
||||
y.storageConfig.NoOverwriteUpload = true
|
||||
}
|
||||
}
|
||||
// 处理个人云和家庭云参数
|
||||
if y.isFamily() && y.RootFolderID == "-11" {
|
||||
y.RootFolderID = ""
|
||||
|
@ -91,13 +101,14 @@ func (y *Cloud189PC) Init(ctx context.Context) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// 创建中转文件夹,防止重名文件
|
||||
// 创建中转文件夹
|
||||
if y.FamilyTransfer {
|
||||
if y.familyTransferFolder, err = y.createFamilyTransferFolder(32); err != nil {
|
||||
if err := y.createFamilyTransferFolder(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 清理转存文件节流
|
||||
y.cleanFamilyTransferFile = utils.NewThrottle2(time.Minute, func() {
|
||||
if err := y.cleanFamilyTransfer(context.TODO()); err != nil {
|
||||
utils.Log.Errorf("cleanFamilyTransferFolderError:%s", err)
|
||||
|
@ -327,35 +338,49 @@ func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
|||
if !isFamily && y.FamilyTransfer {
|
||||
// 修改上传目标为家庭云文件夹
|
||||
transferDstDir := dstDir
|
||||
dstDir = (y.familyTransferFolder.Value).(*Cloud189Folder)
|
||||
y.familyTransferFolder = y.familyTransferFolder.Next()
|
||||
dstDir = y.familyTransferFolder
|
||||
|
||||
// 使用临时文件名
|
||||
srcName := stream.GetName()
|
||||
stream = &WrapFileStreamer{
|
||||
FileStreamer: stream,
|
||||
Name: fmt.Sprintf("0%s.transfer", uuid.NewString()),
|
||||
}
|
||||
|
||||
// 使用家庭云上传
|
||||
isFamily = true
|
||||
overwrite = false
|
||||
|
||||
defer func() {
|
||||
if newObj != nil {
|
||||
// 批量任务有概率删不掉
|
||||
y.cleanFamilyTransferFile()
|
||||
|
||||
// 转存家庭云文件到个人云
|
||||
err = y.SaveFamilyFileToPersonCloud(context.TODO(), y.FamilyID, newObj, transferDstDir, true)
|
||||
|
||||
task := BatchTaskInfo{
|
||||
FileId: newObj.GetID(),
|
||||
FileName: newObj.GetName(),
|
||||
IsFolder: BoolToNumber(newObj.IsDir()),
|
||||
// 删除家庭云源文件
|
||||
go y.Delete(context.TODO(), y.FamilyID, newObj)
|
||||
// 批量任务有概率删不掉
|
||||
go y.cleanFamilyTransferFile()
|
||||
// 转存失败返回错误
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 删除源文件
|
||||
if resp, err := y.CreateBatchTask("DELETE", y.FamilyID, "", nil, task); err == nil {
|
||||
y.WaitBatchTask("DELETE", resp.TaskID, time.Second)
|
||||
// 永久删除
|
||||
if resp, err := y.CreateBatchTask("CLEAR_RECYCLE", y.FamilyID, "", nil, task); err == nil {
|
||||
y.WaitBatchTask("CLEAR_RECYCLE", resp.TaskID, time.Second)
|
||||
// 查找转存文件
|
||||
var file *Cloud189File
|
||||
file, err = y.findFileByName(context.TODO(), newObj.GetName(), transferDstDir.GetID(), false)
|
||||
if err != nil {
|
||||
if err == errs.ObjectNotFound {
|
||||
err = fmt.Errorf("unknown error: No transfer file obtained %s", newObj.GetName())
|
||||
}
|
||||
return
|
||||
}
|
||||
newObj = nil
|
||||
|
||||
// 重命名转存文件
|
||||
newObj, err = y.Rename(context.TODO(), file, srcName)
|
||||
if err != nil {
|
||||
// 重命名失败删除源文件
|
||||
_ = y.Delete(context.TODO(), "", file)
|
||||
}
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||
)
|
||||
|
||||
|
@ -208,3 +209,12 @@ func IF[V any](o bool, t V, f V) V {
|
|||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type WrapFileStreamer struct {
|
||||
model.FileStreamer
|
||||
Name string
|
||||
}
|
||||
|
||||
func (w *WrapFileStreamer) GetName() string {
|
||||
return w.Name
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package _189pc
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"container/ring"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
|
@ -23,6 +22,7 @@ import (
|
|||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"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/internal/setting"
|
||||
|
@ -185,39 +185,9 @@ func (y *Cloud189PC) put(ctx context.Context, url string, headers map[string]str
|
|||
return body, nil
|
||||
}
|
||||
func (y *Cloud189PC) getFiles(ctx context.Context, fileId string, isFamily bool) ([]model.Obj, error) {
|
||||
fullUrl := API_URL
|
||||
if isFamily {
|
||||
fullUrl += "/family/file"
|
||||
}
|
||||
fullUrl += "/listFiles.action"
|
||||
|
||||
res := make([]model.Obj, 0, 130)
|
||||
res := make([]model.Obj, 0, 100)
|
||||
for pageNum := 1; ; pageNum++ {
|
||||
var resp Cloud189FilesResp
|
||||
_, err := y.get(fullUrl, func(r *resty.Request) {
|
||||
r.SetContext(ctx)
|
||||
r.SetQueryParams(map[string]string{
|
||||
"folderId": fileId,
|
||||
"fileType": "0",
|
||||
"mediaAttr": "0",
|
||||
"iconOption": "5",
|
||||
"pageNum": fmt.Sprint(pageNum),
|
||||
"pageSize": "130",
|
||||
})
|
||||
if isFamily {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"familyId": y.FamilyID,
|
||||
"orderBy": toFamilyOrderBy(y.OrderBy),
|
||||
"descending": toDesc(y.OrderDirection),
|
||||
})
|
||||
} else {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"recursive": "0",
|
||||
"orderBy": y.OrderBy,
|
||||
"descending": toDesc(y.OrderDirection),
|
||||
})
|
||||
}
|
||||
}, &resp, isFamily)
|
||||
resp, err := y.getFilesWithPage(ctx, fileId, isFamily, pageNum, 1000, y.OrderBy, y.OrderDirection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -236,6 +206,63 @@ func (y *Cloud189PC) getFiles(ctx context.Context, fileId string, isFamily bool)
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) getFilesWithPage(ctx context.Context, fileId string, isFamily bool, pageNum int, pageSize int, orderBy string, orderDirection string) (*Cloud189FilesResp, error) {
|
||||
fullUrl := API_URL
|
||||
if isFamily {
|
||||
fullUrl += "/family/file"
|
||||
}
|
||||
fullUrl += "/listFiles.action"
|
||||
|
||||
var resp Cloud189FilesResp
|
||||
_, err := y.get(fullUrl, func(r *resty.Request) {
|
||||
r.SetContext(ctx)
|
||||
r.SetQueryParams(map[string]string{
|
||||
"folderId": fileId,
|
||||
"fileType": "0",
|
||||
"mediaAttr": "0",
|
||||
"iconOption": "5",
|
||||
"pageNum": fmt.Sprint(pageNum),
|
||||
"pageSize": fmt.Sprint(pageSize),
|
||||
})
|
||||
if isFamily {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"familyId": y.FamilyID,
|
||||
"orderBy": toFamilyOrderBy(orderBy),
|
||||
"descending": toDesc(orderDirection),
|
||||
})
|
||||
} else {
|
||||
r.SetQueryParams(map[string]string{
|
||||
"recursive": "0",
|
||||
"orderBy": orderBy,
|
||||
"descending": toDesc(orderDirection),
|
||||
})
|
||||
}
|
||||
}, &resp, isFamily)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) findFileByName(ctx context.Context, searchName string, folderId string, isFamily bool) (*Cloud189File, error) {
|
||||
for pageNum := 1; ; pageNum++ {
|
||||
resp, err := y.getFilesWithPage(ctx, folderId, isFamily, pageNum, 10, "filename", "asc")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 获取完毕跳出
|
||||
if resp.FileListAO.Count == 0 {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
for i := 0; i < len(resp.FileListAO.FileList); i++ {
|
||||
file := resp.FileListAO.FileList[i]
|
||||
if file.Name == searchName {
|
||||
return &file, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) login() (err error) {
|
||||
// 初始化登陆所需参数
|
||||
if y.loginParam == nil {
|
||||
|
@ -902,8 +929,7 @@ func (y *Cloud189PC) isLogin() bool {
|
|||
}
|
||||
|
||||
// 创建家庭云中转文件夹
|
||||
func (y *Cloud189PC) createFamilyTransferFolder(count int) (*ring.Ring, error) {
|
||||
folders := ring.New(count)
|
||||
func (y *Cloud189PC) createFamilyTransferFolder() error {
|
||||
var rootFolder Cloud189Folder
|
||||
_, err := y.post(API_URL+"/family/file/createFolder.action", func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
|
@ -912,81 +938,61 @@ func (y *Cloud189PC) createFamilyTransferFolder(count int) (*ring.Ring, error) {
|
|||
})
|
||||
}, &rootFolder, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
folderCount := 0
|
||||
|
||||
// 获取已有目录
|
||||
files, err := y.getFiles(context.TODO(), rootFolder.GetID(), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
if folder, ok := file.(*Cloud189Folder); ok {
|
||||
folders.Value = folder
|
||||
folders = folders.Next()
|
||||
folderCount++
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新的目录
|
||||
for folderCount < count {
|
||||
var newFolder Cloud189Folder
|
||||
_, err := y.post(API_URL+"/family/file/createFolder.action", func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"folderName": uuid.NewString(),
|
||||
"familyId": y.FamilyID,
|
||||
"parentId": rootFolder.GetID(),
|
||||
})
|
||||
}, &newFolder, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
folders.Value = &newFolder
|
||||
folders = folders.Next()
|
||||
folderCount++
|
||||
}
|
||||
return folders, nil
|
||||
y.familyTransferFolder = &rootFolder
|
||||
return nil
|
||||
}
|
||||
|
||||
// 清理中转文件夹
|
||||
func (y *Cloud189PC) cleanFamilyTransfer(ctx context.Context) error {
|
||||
var tasks []BatchTaskInfo
|
||||
r := y.familyTransferFolder
|
||||
for p := r.Next(); p != r; p = p.Next() {
|
||||
folder := p.Value.(*Cloud189Folder)
|
||||
|
||||
files, err := y.getFiles(ctx, folder.GetID(), true)
|
||||
transferFolderId := y.familyTransferFolder.GetID()
|
||||
for pageNum := 1; ; pageNum++ {
|
||||
resp, err := y.getFilesWithPage(ctx, transferFolderId, true, pageNum, 100, "lastOpTime", "asc")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
// 获取完毕跳出
|
||||
if resp.FileListAO.Count == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
var tasks []BatchTaskInfo
|
||||
for i := 0; i < len(resp.FileListAO.FolderList); i++ {
|
||||
folder := resp.FileListAO.FolderList[i]
|
||||
tasks = append(tasks, BatchTaskInfo{
|
||||
FileId: folder.GetID(),
|
||||
FileName: folder.GetName(),
|
||||
IsFolder: BoolToNumber(folder.IsDir()),
|
||||
})
|
||||
}
|
||||
for i := 0; i < len(resp.FileListAO.FileList); i++ {
|
||||
file := resp.FileListAO.FileList[i]
|
||||
tasks = append(tasks, BatchTaskInfo{
|
||||
FileId: file.GetID(),
|
||||
FileName: file.GetName(),
|
||||
IsFolder: BoolToNumber(file.IsDir()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(tasks) > 0 {
|
||||
// 删除
|
||||
resp, err := y.CreateBatchTask("DELETE", y.FamilyID, "", nil, tasks...)
|
||||
if err != nil {
|
||||
if len(tasks) > 0 {
|
||||
// 删除
|
||||
resp, err := y.CreateBatchTask("DELETE", y.FamilyID, "", nil, tasks...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = y.WaitBatchTask("DELETE", resp.TaskID, time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 永久删除
|
||||
resp, err = y.CreateBatchTask("CLEAR_RECYCLE", y.FamilyID, "", nil, tasks...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = y.WaitBatchTask("CLEAR_RECYCLE", resp.TaskID, time.Second)
|
||||
return err
|
||||
}
|
||||
err = y.WaitBatchTask("DELETE", resp.TaskID, time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 永久删除
|
||||
resp, err = y.CreateBatchTask("CLEAR_RECYCLE", y.FamilyID, "", nil, tasks...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = y.WaitBatchTask("CLEAR_RECYCLE", resp.TaskID, time.Second)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1063,6 +1069,34 @@ func (y *Cloud189PC) SaveFamilyFileToPersonCloud(ctx context.Context, familyId s
|
|||
}
|
||||
}
|
||||
|
||||
// 永久删除文件
|
||||
func (y *Cloud189PC) Delete(ctx context.Context, familyId string, srcObj model.Obj) error {
|
||||
task := BatchTaskInfo{
|
||||
FileId: srcObj.GetID(),
|
||||
FileName: srcObj.GetName(),
|
||||
IsFolder: BoolToNumber(srcObj.IsDir()),
|
||||
}
|
||||
// 删除源文件
|
||||
resp, err := y.CreateBatchTask("DELETE", familyId, "", nil, task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = y.WaitBatchTask("DELETE", resp.TaskID, time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 清除回收站
|
||||
resp, err = y.CreateBatchTask("CLEAR_RECYCLE", familyId, "", nil, task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = y.WaitBatchTask("CLEAR_RECYCLE", resp.TaskID, time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) CreateBatchTask(aType string, familyID string, targetFolderId string, other map[string]string, taskInfos ...BatchTaskInfo) (*CreateBatchTaskResp, error) {
|
||||
var resp CreateBatchTaskResp
|
||||
_, err := y.post(API_URL+"/batch/createBatchTask.action", func(req *resty.Request) {
|
||||
|
|
|
@ -34,31 +34,36 @@ func NewDebounce2(interval time.Duration, f func()) func() {
|
|||
if timer == nil {
|
||||
timer = time.AfterFunc(interval, f)
|
||||
}
|
||||
(*time.Timer)(timer).Reset(interval)
|
||||
timer.Reset(interval)
|
||||
}
|
||||
}
|
||||
|
||||
func NewThrottle(interval time.Duration) func(func()) {
|
||||
var lastCall time.Time
|
||||
|
||||
var lock sync.Mutex
|
||||
return func(fn func()) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
if now.Sub(lastCall) < interval {
|
||||
return
|
||||
if now.Sub(lastCall) >= interval {
|
||||
lastCall = now
|
||||
go fn()
|
||||
}
|
||||
time.AfterFunc(interval, fn)
|
||||
lastCall = now
|
||||
}
|
||||
}
|
||||
|
||||
func NewThrottle2(interval time.Duration, fn func()) func() {
|
||||
var lastCall time.Time
|
||||
var lock sync.Mutex
|
||||
return func() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
if now.Sub(lastCall) < interval {
|
||||
return
|
||||
if now.Sub(lastCall) >= interval {
|
||||
lastCall = now
|
||||
go fn()
|
||||
}
|
||||
time.AfterFunc(interval, fn)
|
||||
lastCall = now
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue