diff --git a/drivers/189pc/extension.go b/drivers/189pc/extension.go index 6359ca58..12663c2c 100644 --- a/drivers/189pc/extension.go +++ b/drivers/189pc/extension.go @@ -9,6 +9,12 @@ import ( "encoding/pem" "errors" "fmt" + "net/http" + "strconv" + "strings" + "sync" + "time" + "github.com/OpenListTeam/OpenList/v4/drivers/base" "github.com/OpenListTeam/OpenList/v4/internal/casfile" "github.com/OpenListTeam/OpenList/v4/internal/conf" @@ -17,11 +23,8 @@ import ( "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" - "net/http" - "strconv" - "strings" - "time" ) var linkTransferObj = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (*model.Link, error) { @@ -48,6 +51,7 @@ var readTransferredCASInfo = func(file model.FileStreamer) (*casfile.Info, error } var restoreTransferredCASFromInfo = func(ctx context.Context, y *Cloud189PC, dstDir model.Obj, casFileName string, info *casfile.Info) (model.Obj, error) { + log.Debugf("restoreSourceFromCASInfo: %+v to directory %+v", info, dstDir) return y.restoreSourceFromCASInfo(ctx, dstDir, casFileName, info) } @@ -73,11 +77,21 @@ var restoreTransferredCASAndLink = func(ctx context.Context, y *Cloud189PC, obj Name: conf.TempDirName, IsFolder: true, } + if y.FamilyID != "" { + dstDir = &model.Object{ + ID: y.familyTransferFolder.GetID(), + Name: y.familyTransferFolder.GetName(), + IsFolder: true, + } + forcedDriver.Type = "family" + } + log.Debugf("restore to %v %v", forcedDriver.Type, y.FamilyID) restoredObj, err := restoreTransferredCASFromInfo(ctx, forcedDriver, dstDir, obj.GetName(), info) if err != nil { return nil, nil, err } - link, err := linkTransferObj(ctx, y, restoredObj) + log.Debugf("linkTransferObj: %+v", restoredObj) + link, err := linkTransferObj(ctx, forcedDriver, restoredObj) if err != nil { return nil, nil, err } @@ -85,29 +99,25 @@ var restoreTransferredCASAndLink = func(ctx context.Context, y *Cloud189PC, obj } func cloneDriverForCASRestore(y *Cloud189PC) *Cloud189PC { - // Explicit field copy so we can keep sync.Map at its zero value (sync.Map must not be copied). + // Positional construction keeps this clone in lockstep with Cloud189PC's field list + // while still leaving sync.Map at its zero value (sync.Map must not be copied). return &Cloud189PC{ - Storage: y.Storage, - Addition: y.Addition, - - identity: y.identity, - client: y.client, - - loginParam: y.loginParam, - qrcodeParam: y.qrcodeParam, - - tokenInfo: y.tokenInfo, - - uploadThread: y.uploadThread, - - familyTransferFolder: y.familyTransferFolder, - cleanFamilyTransferFile: y.cleanFamilyTransferFile, - - storageConfig: y.storageConfig, - ref: y.ref, - TempDirId: y.TempDirId, - cron: y.cron, - client2: y.client2, + y.Storage, + y.Addition, + y.identity, + y.client, + y.loginParam, + y.qrcodeParam, + y.tokenInfo, + y.uploadThread, + y.familyTransferFolder, + y.cleanFamilyTransferFile, + y.storageConfig, + y.ref, + y.TempDirId, + y.cron, + y.client2, + sync.Map{}, } } @@ -122,7 +132,28 @@ func (y *Cloud189PC) resolveTransferredShareFile(ctx context.Context, transferFi return link, transferFile, nil } +func (y *Cloud189PC) createFamilyTempDir() error { + var rootFolder Cloud189Folder + _, err := y.post(API_URL+"/family/file/createFolder.action", func(req *resty.Request) { + req.SetQueryParams(map[string]string{ + "folderName": conf.TempDirName, + "familyId": y.FamilyID, + }) + }, &rootFolder, true) + if err != nil { + return err + } + y.familyTransferFolder = &rootFolder + + log.Info("189Cloud family temp folder id: ", rootFolder.GetID()) + return nil +} + func (y *Cloud189PC) createTempDir(ctx context.Context) error { + if y.FamilyID != "" { + y.createFamilyTempDir() + } + dir := &Cloud189File{ ID: "-11", } @@ -241,11 +272,10 @@ func (y *Cloud189PC) Transfer(ctx context.Context, shareId int, fileId string, f return nil, errors.New("no token found") } - isFamily := y.isFamily() other := map[string]string{"shareId": strconv.Itoa(shareId)} log.Debug("create share save task") - resp, err := y.CreateBatchTask("SHARE_SAVE", IF(isFamily, y.FamilyID, ""), y.TempDirId, other, BatchTaskInfo{ + resp, err := y.CreateBatchTask("SHARE_SAVE", "", y.TempDirId, other, BatchTaskInfo{ FileId: fileId, FileName: fileName, IsFolder: 0, @@ -284,6 +314,11 @@ func (y *Cloud189PC) Transfer(ctx context.Context, shareId int, fileId string, f link, cleanupTarget, err := y.resolveTransferredShareFile(ctx, transferFile) if cleanupTarget != nil { + driver := y + if cleanupTarget != transferFile && y.FamilyID != "" { + driver = cloneDriverForCASRestore(y) + driver.Type = "family" + } go func() { delayTime := setting.GetInt(conf.DeleteDelayTime, 900) if delayTime == 0 { @@ -292,25 +327,25 @@ func (y *Cloud189PC) Transfer(ctx context.Context, shareId int, fileId string, f cleanupName := cleanupTarget.GetName() cleanupID := cleanupTarget.GetID() - log.Infof("[%v] Delete 189 temp file %v after %v seconds.", y.ID, cleanupID, delayTime) + log.Infof("[%v] Delete 189 temp file %v after %v seconds.", driver.ID, cleanupID, delayTime) time.Sleep(time.Duration(delayTime) * time.Second) - log.Infof("[%v] Delete 189 temp file: %v %v", y.ID, cleanupID, cleanupName) - removeErr := y.Remove(ctx, cleanupTarget) + log.Infof("[%v] Delete 189 temp file: %v %v", driver.ID, cleanupID, cleanupName) + removeErr := driver.Remove(ctx, cleanupTarget) if removeErr != nil { - log.Infof("[%v] 天翼云盘删除文件:%s失败: %v", y.ID, cleanupName, removeErr) + log.Infof("[%v] 天翼云盘删除文件:%s失败: %v", driver.ID, cleanupName, removeErr) return } - log.Debugf("[%v] 已删除天翼云盘下的文件: %v", y.ID, cleanupName) - _, removeErr = y.CreateBatchTask("CLEAR_RECYCLE", "", "", nil, BatchTaskInfo{ + log.Debugf("[%v] 已删除天翼云盘下的文件: %v", driver.ID, cleanupName) + _, removeErr = driver.CreateBatchTask("CLEAR_RECYCLE", driver.FamilyID, "", nil, BatchTaskInfo{ FileId: cleanupID, FileName: cleanupName, IsFolder: 0, }) if removeErr != nil { - log.Infof("[%v] 天翼云盘清除回收站失败: %v", y.ID, removeErr) + log.Infof("[%v] 天翼云盘清除回收站失败: %v", driver.ID, removeErr) } else { - log.Debugf("[%v] 天翼云盘清除回收站完成", y.ID) + log.Debugf("[%v] 天翼云盘清除回收站完成", driver.ID) } }() } diff --git a/drivers/189pc/extension_test.go b/drivers/189pc/extension_test.go index 5d0083ad..f5500e0c 100644 --- a/drivers/189pc/extension_test.go +++ b/drivers/189pc/extension_test.go @@ -4,14 +4,19 @@ import ( "context" "errors" "io" + "net/http" + "reflect" "sync" "testing" "time" "github.com/OpenListTeam/OpenList/v4/internal/casfile" + "github.com/OpenListTeam/OpenList/v4/internal/driver" "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/pkg/cron" "github.com/OpenListTeam/OpenList/v4/pkg/http_range" "github.com/OpenListTeam/OpenList/v4/pkg/utils" + "github.com/go-resty/resty/v2" ) var linkSeamMu sync.Mutex @@ -190,3 +195,107 @@ func TestResolveTransferredShareFile_CASRestoreFailureReturnsErrorAndDoesNotFall t.Fatalf("expected no fallback link call, got %d", linkCalls) } } + +func TestCloneDriverForCASRestore_ClonesCurrentFieldsAndResetsAutoRestoreState(t *testing.T) { + cleanupCalled := 0 + cleanup := func() { + cleanupCalled++ + } + source := &Cloud189PC{ + Storage: model.Storage{ + ID: 189, + MountPath: "/189pc", + CacheExpiration: 123, + Remark: "test-storage", + }, + Addition: Addition{ + Username: "user", + Password: "pass", + Type: "family", + FamilyID: "family-id", + RestoreSourceUseCurrentName: true, + AutoRestoreExistingCAS: true, + }, + identity: "identity", + client: resty.New(), + loginParam: &LoginParam{RsaUsername: "rsa-user", RsaPassword: "rsa-pass"}, + qrcodeParam: &QRLoginParam{UUID: "uuid"}, + tokenInfo: &AppSessionResp{AccessToken: "access", RefreshToken: "refresh"}, + uploadThread: 9, + familyTransferFolder: &Cloud189Folder{Name: "family-temp"}, + cleanFamilyTransferFile: cleanup, + storageConfig: driver.Config{ + Name: "189CloudPC", + DefaultRoot: "-11", + OnlyProxy: true, + NoOverwriteUpload: true, + }, + TempDirId: "temp-dir-id", + cron: cron.NewCron(time.Minute), + client2: resty.New().SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + })), + } + source.ref = source + source.autoRestoreInFlight.Store("busy.cas", struct{}{}) + + cloned := cloneDriverForCASRestore(source) + if cloned == source { + t.Fatal("expected clone to allocate a distinct driver") + } + if !reflect.DeepEqual(source.Storage, cloned.Storage) { + t.Fatalf("expected storage copied, got %#v", cloned.Storage) + } + if !reflect.DeepEqual(source.Addition, cloned.Addition) { + t.Fatalf("expected addition copied, got %#v", cloned.Addition) + } + if cloned.identity != source.identity { + t.Fatalf("expected identity %q, got %q", source.identity, cloned.identity) + } + if cloned.client != source.client { + t.Fatalf("expected client pointer copied") + } + if cloned.loginParam != source.loginParam { + t.Fatalf("expected loginParam pointer copied") + } + if cloned.qrcodeParam != source.qrcodeParam { + t.Fatalf("expected qrcodeParam pointer copied") + } + if cloned.tokenInfo != source.tokenInfo { + t.Fatalf("expected tokenInfo pointer copied") + } + if cloned.uploadThread != source.uploadThread { + t.Fatalf("expected uploadThread %d, got %d", source.uploadThread, cloned.uploadThread) + } + if cloned.familyTransferFolder != source.familyTransferFolder { + t.Fatalf("expected familyTransferFolder pointer copied") + } + if cloned.cleanFamilyTransferFile == nil { + t.Fatal("expected cleanFamilyTransferFile copied") + } + cloned.cleanFamilyTransferFile() + if cleanupCalled != 1 { + t.Fatalf("expected cloned cleanup func to call original closure once, got %d", cleanupCalled) + } + if !reflect.DeepEqual(source.storageConfig, cloned.storageConfig) { + t.Fatalf("expected storageConfig copied, got %#v", cloned.storageConfig) + } + if cloned.ref != source.ref { + t.Fatalf("expected ref pointer copied") + } + if cloned.TempDirId != source.TempDirId { + t.Fatalf("expected TempDirId %q, got %q", source.TempDirId, cloned.TempDirId) + } + if cloned.cron != source.cron { + t.Fatalf("expected cron pointer copied") + } + if cloned.client2 != source.client2 { + t.Fatalf("expected client2 pointer copied") + } + if !cloned.beginAutoRestore("busy.cas") { + t.Fatal("expected clone autoRestoreInFlight to start empty") + } + if source.beginAutoRestore("busy.cas") { + t.Fatal("expected source autoRestoreInFlight to keep existing state") + } +}