Skip to content

Commit 66d9809

Browse files
feat(strm): strm local file (#1127)
* feat(strm): strm local file * feat: 代码优化 * feat: 访问被strm挂载路径时也更新 * fix: 路径最后带/判断缺失 * fix: 路径最后带/判断缺失 * refactor * refactor * fix: close seekable-stream in `generateStrm` * refactor: lazy create local file * 优化路径判断 --------- Co-authored-by: KirCute <[email protected]>
1 parent db8a7e8 commit 66d9809

File tree

7 files changed

+216
-34
lines changed

7 files changed

+216
-34
lines changed

drivers/strm/driver.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/OpenListTeam/OpenList/v4/internal/stream"
1616
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
1717
"github.com/OpenListTeam/OpenList/v4/server/common"
18+
log "github.com/sirupsen/logrus"
1819
)
1920

2021
type Strm struct {
@@ -40,6 +41,9 @@ func (d *Strm) Init(ctx context.Context) error {
4041
if d.Paths == "" {
4142
return errors.New("paths is required")
4243
}
44+
if d.SaveStrmToLocal && len(d.SaveStrmLocalPath) <= 0 {
45+
return errors.New("SaveStrmLocalPath is required")
46+
}
4347
d.pathMap = make(map[string][]string)
4448
for _, path := range strings.Split(d.Paths, "\n") {
4549
path = strings.TrimSpace(path)
@@ -48,6 +52,11 @@ func (d *Strm) Init(ctx context.Context) error {
4852
}
4953
k, v := getPair(path)
5054
d.pathMap[k] = append(d.pathMap[k], v)
55+
err := InsertStrm(utils.FixAndCleanPath(strings.TrimSpace(path)), d)
56+
if err != nil {
57+
log.Errorf("insert strmTrie error: %v", err)
58+
continue
59+
}
5160
}
5261
if len(d.pathMap) == 1 {
5362
for k := range d.pathMap {
@@ -87,6 +96,9 @@ func (d *Strm) Drop(ctx context.Context) error {
8796
d.pathMap = nil
8897
d.downloadSuffix = nil
8998
d.supportSuffix = nil
99+
for _, path := range strings.Split(d.Paths, "\n") {
100+
RemoveStrm(utils.FixAndCleanPath(strings.TrimSpace(path)), d)
101+
}
90102
return nil
91103
}
92104

drivers/strm/hook.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package strm
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"os"
8+
stdpath "path"
9+
"strings"
10+
11+
"github.com/OpenListTeam/OpenList/v4/internal/model"
12+
"github.com/OpenListTeam/OpenList/v4/internal/op"
13+
"github.com/OpenListTeam/OpenList/v4/internal/stream"
14+
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
15+
log "github.com/sirupsen/logrus"
16+
"github.com/tchap/go-patricia/v2/patricia"
17+
)
18+
19+
var strmTrie = patricia.NewTrie()
20+
21+
func UpdateLocalStrm(ctx context.Context, path string, objs []model.Obj) {
22+
path = utils.FixAndCleanPath(path)
23+
updateLocal := func(driver *Strm, basePath string, objs []model.Obj) {
24+
relParent := strings.TrimPrefix(basePath, driver.MountPath)
25+
localParentPath := stdpath.Join(driver.SaveStrmLocalPath, relParent)
26+
for _, obj := range objs {
27+
localPath := stdpath.Join(localParentPath, obj.GetName())
28+
generateStrm(ctx, driver, obj, localPath)
29+
}
30+
deleteExtraFiles(localParentPath, objs)
31+
}
32+
33+
_ = strmTrie.VisitPrefixes(patricia.Prefix(path), func(needPathPrefix patricia.Prefix, item patricia.Item) error {
34+
strmDrivers := item.([]*Strm)
35+
needPath := string(needPathPrefix)
36+
restPath := strings.TrimPrefix(path, needPath)
37+
if len(restPath) > 0 && restPath[0] != '/' {
38+
return nil
39+
}
40+
for _, strmDriver := range strmDrivers {
41+
strmObjs, _ := utils.SliceConvert(objs, func(obj model.Obj) (model.Obj, error) {
42+
ret := strmDriver.convert2strmObj(ctx, path, obj)
43+
return &ret, nil
44+
})
45+
updateLocal(strmDriver, stdpath.Join(stdpath.Base(needPath), restPath), strmObjs)
46+
}
47+
return nil
48+
})
49+
}
50+
51+
func InsertStrm(dstPath string, d *Strm) error {
52+
prefix := patricia.Prefix(strings.TrimRight(dstPath, "/"))
53+
existing := strmTrie.Get(prefix)
54+
55+
if existing == nil {
56+
if !strmTrie.Insert(prefix, []*Strm{d}) {
57+
return errors.New("failed to insert strm")
58+
}
59+
return nil
60+
}
61+
if lst, ok := existing.([]*Strm); ok {
62+
strmTrie.Set(prefix, append(lst, d))
63+
} else {
64+
return errors.New("invalid trie item type")
65+
}
66+
67+
return nil
68+
}
69+
70+
func RemoveStrm(dstPath string, d *Strm) {
71+
prefix := patricia.Prefix(strings.TrimRight(dstPath, "/"))
72+
existing := strmTrie.Get(prefix)
73+
if existing == nil {
74+
return
75+
}
76+
lst, ok := existing.([]*Strm)
77+
if !ok {
78+
return
79+
}
80+
if len(lst) == 1 && lst[0] == d {
81+
strmTrie.Delete(prefix)
82+
return
83+
}
84+
85+
for i, di := range lst {
86+
if di == d {
87+
newList := append(lst[:i], lst[i+1:]...)
88+
strmTrie.Set(prefix, newList)
89+
return
90+
}
91+
}
92+
}
93+
94+
func generateStrm(ctx context.Context, driver *Strm, obj model.Obj, localPath string) {
95+
link, err := driver.Link(ctx, obj, model.LinkArgs{})
96+
if err != nil {
97+
log.Warnf("failed to generate strm of obj %s: failed to link: %v", localPath, err)
98+
return
99+
}
100+
seekableStream, err := stream.NewSeekableStream(&stream.FileStream{
101+
Obj: obj,
102+
Ctx: ctx,
103+
}, link)
104+
if err != nil {
105+
_ = link.Close()
106+
log.Warnf("failed to generate strm of obj %s: failed to get seekable stream: %v", localPath, err)
107+
return
108+
}
109+
defer seekableStream.Close()
110+
file, err := utils.CreateNestedFile(localPath)
111+
if err != nil {
112+
log.Warnf("failed to generate strm of obj %s: failed to create local file: %v", localPath, err)
113+
return
114+
}
115+
defer file.Close()
116+
if _, err := io.Copy(file, seekableStream); err != nil {
117+
log.Warnf("failed to generate strm of obj %s: copy failed: %v", localPath, err)
118+
}
119+
}
120+
121+
func deleteExtraFiles(localPath string, objs []model.Obj) {
122+
localFiles, err := getLocalFiles(localPath)
123+
if err != nil {
124+
log.Errorf("Failed to read local files from %s: %v", localPath, err)
125+
return
126+
}
127+
128+
objsSet := make(map[string]struct{})
129+
for _, obj := range objs {
130+
if obj.IsDir() {
131+
continue
132+
}
133+
objsSet[stdpath.Join(localPath, obj.GetName())] = struct{}{}
134+
}
135+
136+
for _, localFile := range localFiles {
137+
if _, exists := objsSet[localFile]; !exists {
138+
err := os.Remove(localFile)
139+
if err != nil {
140+
log.Errorf("Failed to delete file: %s, error: %v\n", localFile, err)
141+
} else {
142+
log.Infof("Deleted file %s", localFile)
143+
}
144+
}
145+
}
146+
}
147+
148+
func getLocalFiles(localPath string) ([]string, error) {
149+
var files []string
150+
entries, err := os.ReadDir(localPath)
151+
if err != nil {
152+
return nil, err
153+
}
154+
for _, entry := range entries {
155+
if !entry.IsDir() {
156+
files = append(files, stdpath.Join(localPath, entry.Name()))
157+
}
158+
}
159+
return files, nil
160+
}
161+
162+
func init() {
163+
op.RegisterObjsUpdateHook(UpdateLocalStrm)
164+
}

drivers/strm/meta.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ type Addition struct {
1111
FilterFileTypes string `json:"filterFileTypes" type:"text" default:"strm" required:"false" help:"Supports suffix name of strm file"`
1212
DownloadFileTypes string `json:"downloadFileTypes" type:"text" default:"ass" required:"false" help:"Files need to download with strm (usally subtitles)"`
1313
EncodePath bool `json:"encodePath" default:"true" required:"true" help:"encode the path in the strm file"`
14-
LocalModel bool `json:"localModel" default:"false" help:"enable local mode"`
14+
WithoutUrl bool `json:"withoutUrl" default:"false" help:"strm file content without URL prefix"`
15+
SaveStrmToLocal bool `json:"SaveStrmToLocal" default:"false" help:"save strm file locally"`
16+
SaveStrmLocalPath string `json:"SaveStrmLocalPath" type:"text" help:"save strm file local path"`
1517
}
1618

1719
var config = driver.Config{

drivers/strm/util.go

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -61,36 +61,12 @@ func (d *Strm) list(ctx context.Context, dst, sub string, args *fs.ListArgs) ([]
6161

6262
var validObjs []model.Obj
6363
for _, obj := range objs {
64-
id, name, path := "", obj.GetName(), ""
65-
size := int64(0)
66-
if !obj.IsDir() {
67-
path = stdpath.Join(reqPath, obj.GetName())
68-
ext := strings.ToLower(utils.Ext(name))
69-
if _, ok := d.supportSuffix[ext]; ok {
70-
id = "strm"
71-
name = strings.TrimSuffix(name, ext) + "strm"
72-
size = int64(len(d.getLink(ctx, path)))
73-
} else if _, ok := d.downloadSuffix[ext]; ok {
74-
size = obj.GetSize()
75-
} else {
76-
continue
77-
}
78-
}
79-
objRes := model.Object{
80-
ID: id,
81-
Path: path,
82-
Name: name,
83-
Size: size,
84-
Modified: obj.ModTime(),
85-
IsFolder: obj.IsDir(),
86-
}
87-
64+
objRes := d.convert2strmObj(ctx, reqPath, obj)
8865
thumb, ok := model.GetThumb(obj)
8966
if !ok {
9067
validObjs = append(validObjs, &objRes)
9168
continue
9269
}
93-
9470
validObjs = append(validObjs, &model.ObjThumb{
9571
Object: objRes,
9672
Thumbnail: model.Thumbnail{
@@ -101,6 +77,32 @@ func (d *Strm) list(ctx context.Context, dst, sub string, args *fs.ListArgs) ([]
10177
return validObjs, nil
10278
}
10379

80+
func (d *Strm) convert2strmObj(ctx context.Context, reqPath string, obj model.Obj) model.Object {
81+
id, name, path := "", obj.GetName(), ""
82+
size := int64(0)
83+
if !obj.IsDir() {
84+
path = stdpath.Join(reqPath, obj.GetName())
85+
ext := strings.ToLower(utils.Ext(name))
86+
if _, ok := d.supportSuffix[ext]; ok {
87+
id = "strm"
88+
name = strings.TrimSuffix(name, ext) + "strm"
89+
size = int64(len(d.getLink(ctx, path)))
90+
} else if _, ok := d.downloadSuffix[ext]; ok {
91+
size = obj.GetSize()
92+
} else {
93+
94+
}
95+
}
96+
return model.Object{
97+
ID: id,
98+
Path: path,
99+
Name: name,
100+
Size: size,
101+
Modified: obj.ModTime(),
102+
IsFolder: obj.IsDir(),
103+
}
104+
}
105+
104106
func (d *Strm) getLink(ctx context.Context, path string) string {
105107
finalPath := path
106108
if d.EncodePath {
@@ -110,7 +112,7 @@ func (d *Strm) getLink(ctx context.Context, path string) string {
110112
signPath := sign.Sign(path)
111113
finalPath = fmt.Sprintf("%s?sign=%s", finalPath, signPath)
112114
}
113-
if d.LocalModel {
115+
if d.WithoutUrl {
114116
return finalPath
115117
}
116118
apiUrl := d.SiteUrl
@@ -119,7 +121,9 @@ func (d *Strm) getLink(ctx context.Context, path string) string {
119121
} else {
120122
apiUrl = common.GetApiUrl(ctx)
121123
}
122-
124+
if !strings.HasPrefix(finalPath, "/") {
125+
finalPath = "/" + finalPath
126+
}
123127
return fmt.Sprintf("%s/d%s",
124128
apiUrl,
125129
finalPath)

internal/op/fs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li
5757
model.WrapObjsName(files)
5858
// call hooks
5959
go func(reqPath string, files []model.Obj) {
60-
HandleObjsUpdateHook(reqPath, files)
60+
HandleObjsUpdateHook(context.WithoutCancel(ctx), reqPath, files)
6161
}(utils.GetFullPath(storage.GetStorage().MountPath, path), files)
6262

6363
// sort objs

internal/op/hook.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package op
22

33
import (
4+
"context"
45
"regexp"
56
"strings"
67

@@ -13,7 +14,7 @@ import (
1314
)
1415

1516
// Obj
16-
type ObjsUpdateHook = func(parent string, objs []model.Obj)
17+
type ObjsUpdateHook = func(ctx context.Context, parent string, objs []model.Obj)
1718

1819
var (
1920
objsUpdateHooks = make([]ObjsUpdateHook, 0)
@@ -23,9 +24,9 @@ func RegisterObjsUpdateHook(hook ObjsUpdateHook) {
2324
objsUpdateHooks = append(objsUpdateHooks, hook)
2425
}
2526

26-
func HandleObjsUpdateHook(parent string, objs []model.Obj) {
27+
func HandleObjsUpdateHook(ctx context.Context, parent string, objs []model.Obj) {
2728
for _, hook := range objsUpdateHooks {
28-
hook(parent, objs)
29+
hook(ctx, parent, objs)
2930
}
3031
}
3132

internal/search/build.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,13 @@ func Config(ctx context.Context) searcher.Config {
199199
return instance.Config()
200200
}
201201

202-
func Update(parent string, objs []model.Obj) {
202+
func Update(ctx context.Context, parent string, objs []model.Obj) {
203203
if instance == nil || !instance.Config().AutoUpdate || !setting.GetBool(conf.AutoUpdateIndex) || Running() {
204204
return
205205
}
206206
if isIgnorePath(parent) {
207207
return
208208
}
209-
ctx := context.Background()
210209
// only update when index have built
211210
progress, err := Progress()
212211
if err != nil {

0 commit comments

Comments
 (0)