Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 72 additions & 37 deletions drivers/189pc/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {
Expand All @@ -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)
}

Expand All @@ -73,41 +77,47 @@ 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
}
return link, restoredObj, nil
}

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{},
}
}

Expand All @@ -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",
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}()
}
Expand Down
109 changes: 109 additions & 0 deletions drivers/189pc/extension_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
}
Loading