Skip to content

Commit c955881

Browse files
committed
feat: restore transferred 189pc cas files before linking
1 parent 05cc41b commit c955881

2 files changed

Lines changed: 189 additions & 18 deletions

File tree

drivers/189pc/extension.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
"fmt"
1212
"github.com/OpenListTeam/OpenList/v4/drivers/base"
1313
"github.com/OpenListTeam/OpenList/v4/internal/conf"
14+
"github.com/OpenListTeam/OpenList/v4/internal/casfile"
1415
"github.com/OpenListTeam/OpenList/v4/internal/model"
16+
"github.com/OpenListTeam/OpenList/v4/internal/stream"
1517
"github.com/OpenListTeam/OpenList/v4/internal/setting"
1618
"github.com/OpenListTeam/OpenList/v4/pkg/cron"
1719
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
@@ -26,9 +28,55 @@ var linkTransferObj = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (*
2628
return y.Link(ctx, obj, model.LinkArgs{})
2729
}
2830

31+
var openTransferredCASStream = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (model.FileStreamer, error) {
32+
link, err := y.Link(ctx, obj, model.LinkArgs{})
33+
if err != nil {
34+
return nil, err
35+
}
36+
casStream, err := stream.NewSeekableStream(&stream.FileStream{
37+
Ctx: ctx,
38+
Obj: obj,
39+
}, link)
40+
if err != nil {
41+
return nil, err
42+
}
43+
return casStream, nil
44+
}
45+
46+
var readTransferredCASInfo = func(file model.FileStreamer) (*casfile.Info, error) {
47+
return readCASRestoreInfo(file)
48+
}
49+
50+
var restoreTransferredCASFromInfo = func(ctx context.Context, y *Cloud189PC, dstDir model.Obj, casFileName string, info *casfile.Info) (model.Obj, error) {
51+
return y.restoreSourceFromCASInfo(ctx, dstDir, casFileName, info)
52+
}
53+
2954
var restoreTransferredCASAndLink = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (*model.Link, error) {
30-
// Placeholder stub; Task 1 merely introduces the branch point before real CAS restoration runs.
31-
return y.Link(ctx, obj, model.LinkArgs{})
55+
casStream, err := openTransferredCASStream(ctx, y, obj)
56+
if err != nil {
57+
return nil, err
58+
}
59+
defer casStream.Close()
60+
61+
info, err := readTransferredCASInfo(casStream)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
// Force payload-name semantics for this restore path, regardless of the driver's config.
67+
forcedDriver := *y
68+
forcedDriver.RestoreSourceUseCurrentName = false
69+
70+
dstDir := &model.Object{
71+
ID: y.TempDirId,
72+
Name: conf.TempDirName,
73+
IsFolder: true,
74+
}
75+
restoredObj, err := restoreTransferredCASFromInfo(ctx, &forcedDriver, dstDir, obj.GetName(), info)
76+
if err != nil {
77+
return nil, err
78+
}
79+
return linkTransferObj(ctx, y, restoredObj)
3280
}
3381

3482
func (y *Cloud189PC) linkTransferredShareFile(ctx context.Context, transferFile model.Obj) (*model.Link, error) {

drivers/189pc/extension_test.go

Lines changed: 139 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,61 @@ package _189pc
22

33
import (
44
"context"
5+
"errors"
6+
"io"
57
"sync"
68
"testing"
9+
"time"
710

11+
"github.com/OpenListTeam/OpenList/v4/internal/casfile"
812
"github.com/OpenListTeam/OpenList/v4/internal/model"
13+
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
14+
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
915
)
1016

1117
var linkSeamMu sync.Mutex
1218

13-
func TestLinkTransferredShareFile(t *testing.T) {
19+
type stubFileStreamer struct {
20+
utils.Closers
21+
name string
22+
}
23+
24+
func (s *stubFileStreamer) Read(_ []byte) (int, error) { return 0, errors.New("unexpected read") }
25+
func (s *stubFileStreamer) GetSize() int64 { return 0 }
26+
func (s *stubFileStreamer) GetName() string { return s.name }
27+
func (s *stubFileStreamer) ModTime() time.Time { return time.Time{} }
28+
func (s *stubFileStreamer) CreateTime() time.Time { return time.Time{} }
29+
func (s *stubFileStreamer) IsDir() bool { return false }
30+
func (s *stubFileStreamer) GetHash() utils.HashInfo { return utils.HashInfo{} }
31+
func (s *stubFileStreamer) GetID() string { return "" }
32+
func (s *stubFileStreamer) GetPath() string { return "" }
33+
func (s *stubFileStreamer) GetMimetype() string { return "" }
34+
func (s *stubFileStreamer) NeedStore() bool { return false }
35+
func (s *stubFileStreamer) IsForceStreamUpload() bool { return false }
36+
func (s *stubFileStreamer) GetExist() model.Obj { return nil }
37+
func (s *stubFileStreamer) SetExist(model.Obj) {}
38+
func (s *stubFileStreamer) RangeRead(http_range.Range) (io.Reader, error) {
39+
return nil, errors.New("unexpected rangeread")
40+
}
41+
func (s *stubFileStreamer) CacheFullAndWriter(*model.UpdateProgress, io.Writer) (model.File, error) {
42+
return nil, errors.New("unexpected cache")
43+
}
44+
func (s *stubFileStreamer) GetFile() model.File { return nil }
45+
46+
func TestLinkTransferredShareFile_NonCASUsesDirectLinkSeam(t *testing.T) {
1447
driver := &Cloud189PC{}
1548
nonCAS := &Cloud189File{Name: "movie.mkv"}
16-
casFile := &Cloud189File{Name: "movie.mkv.cas"}
1749

1850
directCalls := 0
19-
restoreCalls := 0
2051

2152
linkSeamMu.Lock()
2253
origLink := linkTransferObj
23-
origRestore := restoreTransferredCASAndLink
2454
linkTransferObj = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (*model.Link, error) {
2555
directCalls++
2656
return &model.Link{URL: "https://example.com/direct"}, nil
2757
}
28-
restoreTransferredCASAndLink = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (*model.Link, error) {
29-
restoreCalls++
30-
return &model.Link{URL: "https://example.com/restored"}, nil
31-
}
3258
t.Cleanup(func() {
3359
linkTransferObj = origLink
34-
restoreTransferredCASAndLink = origRestore
3560
linkSeamMu.Unlock()
3661
})
3762

@@ -42,18 +67,116 @@ func TestLinkTransferredShareFile(t *testing.T) {
4267
if link.URL != "https://example.com/direct" {
4368
t.Fatalf("expected direct link, got %q", link.URL)
4469
}
45-
if directCalls != 1 || restoreCalls != 0 {
46-
t.Fatalf("expected direct path only, got direct=%d restore=%d", directCalls, restoreCalls)
70+
if directCalls != 1 {
71+
t.Fatalf("expected direct link seam once, got %d", directCalls)
72+
}
73+
}
74+
75+
func TestLinkTransferredShareFile_CASRestoresPayloadNameEvenWhenDriverUsesCurrentName(t *testing.T) {
76+
driver := &Cloud189PC{
77+
Addition: Addition{RestoreSourceUseCurrentName: true},
78+
TempDirId: "temp-dir-id",
4779
}
80+
casObj := &Cloud189File{Name: "renamed.mkv.cas"}
4881

49-
link, err = driver.linkTransferredShareFile(context.Background(), casFile)
82+
openCalls := 0
83+
readCalls := 0
84+
restoreCalls := 0
85+
linkCalls := 0
86+
87+
linkSeamMu.Lock()
88+
origOpen := openTransferredCASStream
89+
origRead := readTransferredCASInfo
90+
origRestore := restoreTransferredCASFromInfo
91+
origLink := linkTransferObj
92+
openTransferredCASStream = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (model.FileStreamer, error) {
93+
openCalls++
94+
return &stubFileStreamer{name: obj.GetName()}, nil
95+
}
96+
readTransferredCASInfo = func(stream model.FileStreamer) (*casfile.Info, error) {
97+
readCalls++
98+
return &casfile.Info{Name: "payload.mkv", Size: 7, MD5: "abc", SliceMD5: "def"}, nil
99+
}
100+
restoreTransferredCASFromInfo = func(ctx context.Context, y *Cloud189PC, dstDir model.Obj, casFileName string, info *casfile.Info) (model.Obj, error) {
101+
restoreCalls++
102+
if y.RestoreSourceUseCurrentName {
103+
t.Fatalf("expected RestoreSourceUseCurrentName forced false, got true")
104+
}
105+
if dstDir.GetID() != driver.TempDirId {
106+
t.Fatalf("expected restore dst dir id %q, got %q", driver.TempDirId, dstDir.GetID())
107+
}
108+
if casFileName != casObj.GetName() {
109+
t.Fatalf("expected cas file name %q, got %q", casObj.GetName(), casFileName)
110+
}
111+
if info == nil || info.Name != "payload.mkv" {
112+
t.Fatalf("expected payload info, got %#v", info)
113+
}
114+
return &Cloud189File{ID: "restored-id", Name: "payload.mkv"}, nil
115+
}
116+
linkTransferObj = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (*model.Link, error) {
117+
linkCalls++
118+
return &model.Link{URL: "https://example.com/" + obj.GetName()}, nil
119+
}
120+
t.Cleanup(func() {
121+
openTransferredCASStream = origOpen
122+
readTransferredCASInfo = origRead
123+
restoreTransferredCASFromInfo = origRestore
124+
linkTransferObj = origLink
125+
linkSeamMu.Unlock()
126+
})
127+
128+
link, err := driver.linkTransferredShareFile(context.Background(), casObj)
50129
if err != nil {
51130
t.Fatalf("link cas transfer: %v", err)
52131
}
53-
if link.URL != "https://example.com/restored" {
54-
t.Fatalf("expected restored link, got %q", link.URL)
132+
if link.URL != "https://example.com/payload.mkv" {
133+
t.Fatalf("expected restored payload link, got %q", link.URL)
134+
}
135+
if openCalls != 1 || readCalls != 1 || restoreCalls != 1 || linkCalls != 1 {
136+
t.Fatalf("expected open/read/restore/link once, got open=%d read=%d restore=%d link=%d", openCalls, readCalls, restoreCalls, linkCalls)
137+
}
138+
}
139+
140+
func TestLinkTransferredShareFile_CASRestoreFailureReturnsErrorAndDoesNotFallback(t *testing.T) {
141+
driver := &Cloud189PC{TempDirId: "temp-dir-id"}
142+
casObj := &Cloud189File{Name: "movie.mkv.cas"}
143+
144+
linkCalls := 0
145+
146+
linkSeamMu.Lock()
147+
origOpen := openTransferredCASStream
148+
origRead := readTransferredCASInfo
149+
origRestore := restoreTransferredCASFromInfo
150+
origLink := linkTransferObj
151+
openTransferredCASStream = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (model.FileStreamer, error) {
152+
return &stubFileStreamer{name: obj.GetName()}, nil
153+
}
154+
readTransferredCASInfo = func(stream model.FileStreamer) (*casfile.Info, error) {
155+
return &casfile.Info{Name: "payload.mkv", Size: 7, MD5: "abc", SliceMD5: "def"}, nil
156+
}
157+
restoreTransferredCASFromInfo = func(ctx context.Context, y *Cloud189PC, dstDir model.Obj, casFileName string, info *casfile.Info) (model.Obj, error) {
158+
return nil, errors.New("restore failed")
159+
}
160+
linkTransferObj = func(ctx context.Context, y *Cloud189PC, obj model.Obj) (*model.Link, error) {
161+
linkCalls++
162+
return &model.Link{URL: "https://example.com/" + obj.GetName()}, nil
163+
}
164+
t.Cleanup(func() {
165+
openTransferredCASStream = origOpen
166+
readTransferredCASInfo = origRead
167+
restoreTransferredCASFromInfo = origRestore
168+
linkTransferObj = origLink
169+
linkSeamMu.Unlock()
170+
})
171+
172+
link, err := driver.linkTransferredShareFile(context.Background(), casObj)
173+
if err == nil || err.Error() != "restore failed" {
174+
t.Fatalf("expected restore failed error, got %v", err)
175+
}
176+
if link != nil {
177+
t.Fatalf("expected nil link on restore failure, got %#v", link)
55178
}
56-
if directCalls != 1 || restoreCalls != 1 {
57-
t.Fatalf("expected cas restore path once, got direct=%d restore=%d", directCalls, restoreCalls)
179+
if linkCalls != 0 {
180+
t.Fatalf("expected no fallback link call, got %d", linkCalls)
58181
}
59182
}

0 commit comments

Comments
 (0)