Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/internal/search/meilisearch/search.go
1562 views
1
package meilisearch
2
3
import (
4
"context"
5
"fmt"
6
"github.com/alist-org/alist/v3/internal/model"
7
"github.com/alist-org/alist/v3/internal/search/searcher"
8
"github.com/alist-org/alist/v3/pkg/utils"
9
"github.com/google/uuid"
10
"github.com/meilisearch/meilisearch-go"
11
"path"
12
"strings"
13
"time"
14
)
15
16
type searchDocument struct {
17
ID string `json:"id"`
18
model.SearchNode
19
}
20
21
type Meilisearch struct {
22
Client *meilisearch.Client
23
IndexUid string
24
FilterableAttributes []string
25
SearchableAttributes []string
26
}
27
28
func (m *Meilisearch) Config() searcher.Config {
29
return config
30
}
31
32
func (m *Meilisearch) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
33
mReq := &meilisearch.SearchRequest{
34
AttributesToSearchOn: m.SearchableAttributes,
35
Page: int64(req.Page),
36
HitsPerPage: int64(req.PerPage),
37
}
38
if req.Scope != 0 {
39
mReq.Filter = fmt.Sprintf("is_dir = %v", req.Scope == 1)
40
}
41
search, err := m.Client.Index(m.IndexUid).Search(req.Keywords, mReq)
42
if err != nil {
43
return nil, 0, err
44
}
45
nodes, err := utils.SliceConvert(search.Hits, func(src any) (model.SearchNode, error) {
46
srcMap := src.(map[string]any)
47
return model.SearchNode{
48
Parent: srcMap["parent"].(string),
49
Name: srcMap["name"].(string),
50
IsDir: srcMap["is_dir"].(bool),
51
Size: int64(srcMap["size"].(float64)),
52
}, nil
53
})
54
if err != nil {
55
return nil, 0, err
56
}
57
return nodes, search.TotalHits, nil
58
}
59
60
func (m *Meilisearch) Index(ctx context.Context, node model.SearchNode) error {
61
return m.BatchIndex(ctx, []model.SearchNode{node})
62
}
63
64
func (m *Meilisearch) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
65
documents, _ := utils.SliceConvert(nodes, func(src model.SearchNode) (*searchDocument, error) {
66
67
return &searchDocument{
68
ID: uuid.NewString(),
69
SearchNode: src,
70
}, nil
71
})
72
73
_, err := m.Client.Index(m.IndexUid).AddDocuments(documents)
74
if err != nil {
75
return err
76
}
77
78
//// Wait for the task to complete and check
79
//forTask, err := m.Client.WaitForTask(task.TaskUID, meilisearch.WaitParams{
80
// Context: ctx,
81
// Interval: time.Millisecond * 50,
82
//})
83
//if err != nil {
84
// return err
85
//}
86
//if forTask.Status != meilisearch.TaskStatusSucceeded {
87
// return fmt.Errorf("BatchIndex failed, task status is %s", forTask.Status)
88
//}
89
return nil
90
}
91
92
func (m *Meilisearch) getDocumentsByParent(ctx context.Context, parent string) ([]*searchDocument, error) {
93
var result meilisearch.DocumentsResult
94
err := m.Client.Index(m.IndexUid).GetDocuments(&meilisearch.DocumentsQuery{
95
Filter: fmt.Sprintf("parent = '%s'", strings.ReplaceAll(parent, "'", "\\'")),
96
Limit: int64(model.MaxInt),
97
}, &result)
98
if err != nil {
99
return nil, err
100
}
101
return utils.SliceConvert(result.Results, func(src map[string]any) (*searchDocument, error) {
102
return &searchDocument{
103
ID: src["id"].(string),
104
SearchNode: model.SearchNode{
105
Parent: src["parent"].(string),
106
Name: src["name"].(string),
107
IsDir: src["is_dir"].(bool),
108
Size: int64(src["size"].(float64)),
109
},
110
}, nil
111
})
112
}
113
114
func (m *Meilisearch) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
115
result, err := m.getDocumentsByParent(ctx, parent)
116
if err != nil {
117
return nil, err
118
}
119
return utils.SliceConvert(result, func(src *searchDocument) (model.SearchNode, error) {
120
return src.SearchNode, nil
121
})
122
123
}
124
125
func (m *Meilisearch) getParentsByPrefix(ctx context.Context, parent string) ([]string, error) {
126
select {
127
case <-ctx.Done():
128
return nil, ctx.Err()
129
default:
130
parents := []string{parent}
131
get, err := m.getDocumentsByParent(ctx, parent)
132
if err != nil {
133
return nil, err
134
}
135
for _, node := range get {
136
if node.IsDir {
137
arr, err := m.getParentsByPrefix(ctx, path.Join(node.Parent, node.Name))
138
if err != nil {
139
return nil, err
140
}
141
parents = append(parents, arr...)
142
}
143
}
144
return parents, nil
145
}
146
}
147
148
func (m *Meilisearch) DelDirChild(ctx context.Context, prefix string) error {
149
dfs, err := m.getParentsByPrefix(ctx, utils.FixAndCleanPath(prefix))
150
if err != nil {
151
return err
152
}
153
utils.SliceReplace(dfs, func(src string) string {
154
return "'" + strings.ReplaceAll(src, "'", "\\'") + "'"
155
})
156
s := fmt.Sprintf("parent IN [%s]", strings.Join(dfs, ","))
157
task, err := m.Client.Index(m.IndexUid).DeleteDocumentsByFilter(s)
158
if err != nil {
159
return err
160
}
161
taskStatus, err := m.getTaskStatus(ctx, task.TaskUID)
162
if err != nil {
163
return err
164
}
165
if taskStatus != meilisearch.TaskStatusSucceeded {
166
return fmt.Errorf("DelDir failed, task status is %s", taskStatus)
167
}
168
return nil
169
}
170
171
func (m *Meilisearch) Del(ctx context.Context, prefix string) error {
172
prefix = utils.FixAndCleanPath(prefix)
173
dir, name := path.Split(prefix)
174
get, err := m.getDocumentsByParent(ctx, dir[:len(dir)-1])
175
if err != nil {
176
return err
177
}
178
var document *searchDocument
179
for _, v := range get {
180
if v.Name == name {
181
document = v
182
break
183
}
184
}
185
if document == nil {
186
// Defensive programming. Document may be the folder, try deleting Child
187
return m.DelDirChild(ctx, prefix)
188
}
189
if document.IsDir {
190
err = m.DelDirChild(ctx, prefix)
191
if err != nil {
192
return err
193
}
194
}
195
task, err := m.Client.Index(m.IndexUid).DeleteDocument(document.ID)
196
if err != nil {
197
return err
198
}
199
taskStatus, err := m.getTaskStatus(ctx, task.TaskUID)
200
if err != nil {
201
return err
202
}
203
if taskStatus != meilisearch.TaskStatusSucceeded {
204
return fmt.Errorf("DelDir failed, task status is %s", taskStatus)
205
}
206
return nil
207
}
208
209
func (m *Meilisearch) Release(ctx context.Context) error {
210
return nil
211
}
212
213
func (m *Meilisearch) Clear(ctx context.Context) error {
214
_, err := m.Client.Index(m.IndexUid).DeleteAllDocuments()
215
return err
216
}
217
218
func (m *Meilisearch) getTaskStatus(ctx context.Context, taskUID int64) (meilisearch.TaskStatus, error) {
219
forTask, err := m.Client.WaitForTask(taskUID, meilisearch.WaitParams{
220
Context: ctx,
221
Interval: time.Second,
222
})
223
if err != nil {
224
return meilisearch.TaskStatusUnknown, err
225
}
226
return forTask.Status, nil
227
}
228
229