2022-11-28 05:45:25 +00:00
|
|
|
package search
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
2022-12-05 07:46:34 +00:00
|
|
|
"strings"
|
|
|
|
"sync/atomic"
|
2022-11-28 05:45:25 +00:00
|
|
|
"time"
|
|
|
|
|
2022-12-24 12:23:04 +00:00
|
|
|
"github.com/alist-org/alist/v3/internal/conf"
|
2022-11-28 05:45:25 +00:00
|
|
|
"github.com/alist-org/alist/v3/internal/fs"
|
|
|
|
"github.com/alist-org/alist/v3/internal/model"
|
2022-12-05 08:45:11 +00:00
|
|
|
"github.com/alist-org/alist/v3/internal/op"
|
2022-12-24 12:23:04 +00:00
|
|
|
"github.com/alist-org/alist/v3/internal/search/searcher"
|
2023-01-06 16:59:30 +00:00
|
|
|
"github.com/alist-org/alist/v3/internal/setting"
|
2022-12-05 07:46:34 +00:00
|
|
|
"github.com/alist-org/alist/v3/pkg/mq"
|
|
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
2022-12-05 08:45:11 +00:00
|
|
|
mapset "github.com/deckarep/golang-set/v2"
|
2022-11-28 05:45:25 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2022-12-05 07:46:34 +00:00
|
|
|
Running = atomic.Bool{}
|
2022-12-05 05:28:39 +00:00
|
|
|
Quit chan struct{}
|
2022-11-28 05:45:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func BuildIndex(ctx context.Context, indexPaths, ignorePaths []string, maxDepth int, count bool) error {
|
|
|
|
var (
|
2022-12-05 08:45:11 +00:00
|
|
|
err error
|
2022-12-05 05:28:39 +00:00
|
|
|
objCount uint64 = 0
|
|
|
|
fi model.Obj
|
2022-11-28 05:45:25 +00:00
|
|
|
)
|
2023-01-19 09:00:49 +00:00
|
|
|
log.Infof("build index for: %+v", indexPaths)
|
|
|
|
log.Infof("ignore paths: %+v", ignorePaths)
|
2022-12-05 07:46:34 +00:00
|
|
|
Running.Store(true)
|
2022-12-05 05:28:39 +00:00
|
|
|
Quit = make(chan struct{}, 1)
|
2022-12-05 07:46:34 +00:00
|
|
|
indexMQ := mq.NewInMemoryMQ[ObjWithParent]()
|
2022-12-05 05:28:39 +00:00
|
|
|
go func() {
|
2022-12-09 02:02:13 +00:00
|
|
|
ticker := time.NewTicker(time.Second)
|
|
|
|
tickCount := 0
|
2022-12-05 05:28:39 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2022-12-09 02:02:13 +00:00
|
|
|
tickCount += 1
|
|
|
|
if indexMQ.Len() < 1000 && tickCount != 5 {
|
|
|
|
continue
|
|
|
|
} else if tickCount >= 5 {
|
|
|
|
tickCount = 0
|
|
|
|
}
|
2022-12-05 05:28:39 +00:00
|
|
|
log.Infof("index obj count: %d", objCount)
|
2022-12-05 07:46:34 +00:00
|
|
|
indexMQ.ConsumeAll(func(messages []mq.Message[ObjWithParent]) {
|
|
|
|
if len(messages) != 0 {
|
|
|
|
log.Debugf("current index: %s", messages[len(messages)-1].Content.Parent)
|
|
|
|
}
|
|
|
|
if err = BatchIndex(ctx, utils.MustSliceConvert(messages,
|
|
|
|
func(src mq.Message[ObjWithParent]) ObjWithParent {
|
|
|
|
return src.Content
|
|
|
|
})); err != nil {
|
|
|
|
log.Errorf("build index in batch error: %+v", err)
|
|
|
|
} else {
|
|
|
|
objCount = objCount + uint64(len(messages))
|
|
|
|
}
|
|
|
|
if count {
|
|
|
|
WriteProgress(&model.IndexProgress{
|
|
|
|
ObjCount: objCount,
|
|
|
|
IsDone: false,
|
|
|
|
LastDoneTime: nil,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-12-05 05:28:39 +00:00
|
|
|
case <-Quit:
|
2022-12-05 07:46:34 +00:00
|
|
|
Running.Store(false)
|
2022-12-05 05:28:39 +00:00
|
|
|
ticker.Stop()
|
|
|
|
eMsg := ""
|
|
|
|
now := time.Now()
|
|
|
|
originErr := err
|
2022-12-05 07:46:34 +00:00
|
|
|
indexMQ.ConsumeAll(func(messages []mq.Message[ObjWithParent]) {
|
|
|
|
if err = BatchIndex(ctx, utils.MustSliceConvert(messages,
|
|
|
|
func(src mq.Message[ObjWithParent]) ObjWithParent {
|
|
|
|
return src.Content
|
|
|
|
})); err != nil {
|
|
|
|
log.Errorf("build index in batch error: %+v", err)
|
|
|
|
} else {
|
|
|
|
objCount = objCount + uint64(len(messages))
|
|
|
|
}
|
|
|
|
if originErr != nil {
|
2022-12-05 12:18:50 +00:00
|
|
|
log.Errorf("build index error: %+v", originErr)
|
|
|
|
eMsg = originErr.Error()
|
2022-12-05 07:46:34 +00:00
|
|
|
} else {
|
|
|
|
log.Infof("success build index, count: %d", objCount)
|
|
|
|
}
|
|
|
|
if count {
|
|
|
|
WriteProgress(&model.IndexProgress{
|
|
|
|
ObjCount: objCount,
|
|
|
|
IsDone: true,
|
|
|
|
LastDoneTime: &now,
|
|
|
|
Error: eMsg,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
2022-12-05 05:28:39 +00:00
|
|
|
return
|
|
|
|
}
|
2022-11-28 05:45:25 +00:00
|
|
|
}
|
2022-12-05 05:28:39 +00:00
|
|
|
}()
|
|
|
|
defer func() {
|
2022-12-05 07:46:34 +00:00
|
|
|
if Running.Load() {
|
2022-12-05 05:28:39 +00:00
|
|
|
Quit <- struct{}{}
|
2022-11-28 05:45:25 +00:00
|
|
|
}
|
|
|
|
}()
|
2022-12-18 11:51:20 +00:00
|
|
|
admin, err := op.GetAdmin()
|
2022-11-28 05:45:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if count {
|
|
|
|
WriteProgress(&model.IndexProgress{
|
|
|
|
ObjCount: 0,
|
|
|
|
IsDone: false,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
for _, indexPath := range indexPaths {
|
2022-12-05 05:28:39 +00:00
|
|
|
walkFn := func(indexPath string, info model.Obj) error {
|
2022-12-05 07:46:34 +00:00
|
|
|
if !Running.Load() {
|
|
|
|
return filepath.SkipDir
|
|
|
|
}
|
2022-11-28 05:45:25 +00:00
|
|
|
for _, avoidPath := range ignorePaths {
|
2022-12-05 07:46:34 +00:00
|
|
|
if strings.HasPrefix(indexPath, avoidPath) {
|
2022-11-28 05:45:25 +00:00
|
|
|
return filepath.SkipDir
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// ignore root
|
|
|
|
if indexPath == "/" {
|
|
|
|
return nil
|
|
|
|
}
|
2022-12-05 07:46:34 +00:00
|
|
|
indexMQ.Publish(mq.Message[ObjWithParent]{
|
|
|
|
Content: ObjWithParent{
|
|
|
|
Obj: info,
|
|
|
|
Parent: path.Dir(indexPath),
|
|
|
|
},
|
|
|
|
})
|
2022-11-28 05:45:25 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-04-06 16:02:07 +00:00
|
|
|
fi, err = fs.Get(ctx, indexPath, &fs.GetArgs{})
|
2022-11-28 05:45:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// TODO: run walkFS concurrently
|
|
|
|
err = fs.WalkFS(context.WithValue(ctx, "user", admin), maxDepth, indexPath, fi, walkFn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-07 02:41:52 +00:00
|
|
|
func Del(ctx context.Context, prefix string) error {
|
|
|
|
return instance.Del(ctx, prefix)
|
|
|
|
}
|
|
|
|
|
2022-11-28 05:45:25 +00:00
|
|
|
func Clear(ctx context.Context) error {
|
|
|
|
return instance.Clear(ctx)
|
|
|
|
}
|
2022-12-05 08:45:11 +00:00
|
|
|
|
2022-12-24 12:23:04 +00:00
|
|
|
func Config(ctx context.Context) searcher.Config {
|
|
|
|
return instance.Config()
|
|
|
|
}
|
|
|
|
|
2022-12-05 08:45:11 +00:00
|
|
|
func Update(parent string, objs []model.Obj) {
|
2023-01-06 16:59:30 +00:00
|
|
|
if instance == nil || !instance.Config().AutoUpdate || !setting.GetBool(conf.AutoUpdateIndex) || Running.Load() {
|
2022-12-05 08:45:11 +00:00
|
|
|
return
|
|
|
|
}
|
2022-12-24 12:23:04 +00:00
|
|
|
if isIgnorePath(parent) {
|
2022-12-05 08:45:11 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// only update when index have built
|
|
|
|
progress, err := Progress()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("update search index error while get progress: %+v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !progress.IsDone {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
nodes, err := instance.Get(ctx, parent)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("update search index error while get nodes: %+v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
now := mapset.NewSet[string]()
|
|
|
|
for i := range objs {
|
|
|
|
now.Add(objs[i].GetName())
|
|
|
|
}
|
|
|
|
old := mapset.NewSet[string]()
|
|
|
|
for i := range nodes {
|
|
|
|
old.Add(nodes[i].Name)
|
|
|
|
}
|
|
|
|
// delete data that no longer exists
|
|
|
|
toDelete := old.Difference(now)
|
|
|
|
toAdd := now.Difference(old)
|
|
|
|
for i := range nodes {
|
2022-12-11 06:59:58 +00:00
|
|
|
if toDelete.Contains(nodes[i].Name) && !op.HasStorage(path.Join(parent, nodes[i].Name)) {
|
2022-12-05 12:23:37 +00:00
|
|
|
log.Debugf("delete index: %s", path.Join(parent, nodes[i].Name))
|
2022-12-05 08:45:11 +00:00
|
|
|
err = instance.Del(ctx, path.Join(parent, nodes[i].Name))
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("update search index error while del old node: %+v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := range objs {
|
|
|
|
if toAdd.Contains(objs[i].GetName()) {
|
2022-12-05 12:23:37 +00:00
|
|
|
log.Debugf("add index: %s", path.Join(parent, objs[i].GetName()))
|
2022-12-05 08:45:11 +00:00
|
|
|
err = Index(ctx, parent, objs[i])
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("update search index error while index new node: %+v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// build index if it's a folder
|
|
|
|
if objs[i].IsDir() {
|
2023-01-17 09:33:18 +00:00
|
|
|
dir := path.Join(parent, objs[i].GetName())
|
2022-12-24 12:23:04 +00:00
|
|
|
err = BuildIndex(ctx,
|
2023-01-17 09:33:18 +00:00
|
|
|
[]string{dir},
|
2022-12-24 12:23:04 +00:00
|
|
|
conf.SlicesMap[conf.IgnorePaths],
|
2023-01-17 09:33:18 +00:00
|
|
|
setting.GetInt(conf.MaxIndexDepth, 20)-strings.Count(dir, "/"), false)
|
2022-12-05 08:45:11 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("update search index error while build index: %+v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
op.RegisterObjsUpdateHook(Update)
|
|
|
|
}
|