mirror of https://github.com/Xhofe/alist
				
				
				
			
		
			
				
	
	
		
			228 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
package meilisearch
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"github.com/alist-org/alist/v3/internal/model"
 | 
						|
	"github.com/alist-org/alist/v3/internal/search/searcher"
 | 
						|
	"github.com/alist-org/alist/v3/pkg/utils"
 | 
						|
	"github.com/google/uuid"
 | 
						|
	"github.com/meilisearch/meilisearch-go"
 | 
						|
	"path"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
type searchDocument struct {
 | 
						|
	ID string `json:"id"`
 | 
						|
	model.SearchNode
 | 
						|
}
 | 
						|
 | 
						|
type Meilisearch struct {
 | 
						|
	Client               *meilisearch.Client
 | 
						|
	IndexUid             string
 | 
						|
	FilterableAttributes []string
 | 
						|
	SearchableAttributes []string
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) Config() searcher.Config {
 | 
						|
	return config
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
 | 
						|
	mReq := &meilisearch.SearchRequest{
 | 
						|
		AttributesToSearchOn: m.SearchableAttributes,
 | 
						|
		Page:                 int64(req.Page),
 | 
						|
		HitsPerPage:          int64(req.PerPage),
 | 
						|
	}
 | 
						|
	if req.Scope != 0 {
 | 
						|
		mReq.Filter = fmt.Sprintf("is_dir = %v", req.Scope == 1)
 | 
						|
	}
 | 
						|
	search, err := m.Client.Index(m.IndexUid).Search(req.Keywords, mReq)
 | 
						|
	if err != nil {
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
	nodes, err := utils.SliceConvert(search.Hits, func(src any) (model.SearchNode, error) {
 | 
						|
		srcMap := src.(map[string]any)
 | 
						|
		return model.SearchNode{
 | 
						|
			Parent: srcMap["parent"].(string),
 | 
						|
			Name:   srcMap["name"].(string),
 | 
						|
			IsDir:  srcMap["is_dir"].(bool),
 | 
						|
			Size:   int64(srcMap["size"].(float64)),
 | 
						|
		}, nil
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, 0, err
 | 
						|
	}
 | 
						|
	return nodes, search.TotalHits, nil
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) Index(ctx context.Context, node model.SearchNode) error {
 | 
						|
	return m.BatchIndex(ctx, []model.SearchNode{node})
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
 | 
						|
	documents, _ := utils.SliceConvert(nodes, func(src model.SearchNode) (*searchDocument, error) {
 | 
						|
 | 
						|
		return &searchDocument{
 | 
						|
			ID:         uuid.NewString(),
 | 
						|
			SearchNode: src,
 | 
						|
		}, nil
 | 
						|
	})
 | 
						|
 | 
						|
	_, err := m.Client.Index(m.IndexUid).AddDocuments(documents)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	//// Wait for the task to complete and check
 | 
						|
	//forTask, err := m.Client.WaitForTask(task.TaskUID, meilisearch.WaitParams{
 | 
						|
	//	Context:  ctx,
 | 
						|
	//	Interval: time.Millisecond * 50,
 | 
						|
	//})
 | 
						|
	//if err != nil {
 | 
						|
	//	return err
 | 
						|
	//}
 | 
						|
	//if forTask.Status != meilisearch.TaskStatusSucceeded {
 | 
						|
	//	return fmt.Errorf("BatchIndex failed, task status is %s", forTask.Status)
 | 
						|
	//}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) getDocumentsByParent(ctx context.Context, parent string) ([]*searchDocument, error) {
 | 
						|
	var result meilisearch.DocumentsResult
 | 
						|
	err := m.Client.Index(m.IndexUid).GetDocuments(&meilisearch.DocumentsQuery{
 | 
						|
		Filter: fmt.Sprintf("parent = '%s'", strings.ReplaceAll(parent, "'", "\\'")),
 | 
						|
		Limit:  int64(model.MaxInt),
 | 
						|
	}, &result)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return utils.SliceConvert(result.Results, func(src map[string]any) (*searchDocument, error) {
 | 
						|
		return &searchDocument{
 | 
						|
			ID: src["id"].(string),
 | 
						|
			SearchNode: model.SearchNode{
 | 
						|
				Parent: src["parent"].(string),
 | 
						|
				Name:   src["name"].(string),
 | 
						|
				IsDir:  src["is_dir"].(bool),
 | 
						|
				Size:   int64(src["size"].(float64)),
 | 
						|
			},
 | 
						|
		}, nil
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
 | 
						|
	result, err := m.getDocumentsByParent(ctx, parent)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return utils.SliceConvert(result, func(src *searchDocument) (model.SearchNode, error) {
 | 
						|
		return src.SearchNode, nil
 | 
						|
	})
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) getParentsByPrefix(ctx context.Context, parent string) ([]string, error) {
 | 
						|
	select {
 | 
						|
	case <-ctx.Done():
 | 
						|
		return nil, ctx.Err()
 | 
						|
	default:
 | 
						|
		parents := []string{parent}
 | 
						|
		get, err := m.getDocumentsByParent(ctx, parent)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		for _, node := range get {
 | 
						|
			if node.IsDir {
 | 
						|
				arr, err := m.getParentsByPrefix(ctx, path.Join(node.Parent, node.Name))
 | 
						|
				if err != nil {
 | 
						|
					return nil, err
 | 
						|
				}
 | 
						|
				parents = append(parents, arr...)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return parents, nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) DelDirChild(ctx context.Context, prefix string) error {
 | 
						|
	dfs, err := m.getParentsByPrefix(ctx, utils.FixAndCleanPath(prefix))
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	utils.SliceReplace(dfs, func(src string) string {
 | 
						|
		return "'" + strings.ReplaceAll(src, "'", "\\'") + "'"
 | 
						|
	})
 | 
						|
	s := fmt.Sprintf("parent IN [%s]", strings.Join(dfs, ","))
 | 
						|
	task, err := m.Client.Index(m.IndexUid).DeleteDocumentsByFilter(s)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	taskStatus, err := m.getTaskStatus(ctx, task.TaskUID)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if taskStatus != meilisearch.TaskStatusSucceeded {
 | 
						|
		return fmt.Errorf("DelDir failed, task status is %s", taskStatus)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) Del(ctx context.Context, prefix string) error {
 | 
						|
	prefix = utils.FixAndCleanPath(prefix)
 | 
						|
	dir, name := path.Split(prefix)
 | 
						|
	get, err := m.getDocumentsByParent(ctx, dir[:len(dir)-1])
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	var document *searchDocument
 | 
						|
	for _, v := range get {
 | 
						|
		if v.Name == name {
 | 
						|
			document = v
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if document == nil {
 | 
						|
		// Defensive programming. Document may be the folder, try deleting Child
 | 
						|
		return m.DelDirChild(ctx, prefix)
 | 
						|
	}
 | 
						|
	if document.IsDir {
 | 
						|
		err = m.DelDirChild(ctx, prefix)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	task, err := m.Client.Index(m.IndexUid).DeleteDocument(document.ID)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	taskStatus, err := m.getTaskStatus(ctx, task.TaskUID)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if taskStatus != meilisearch.TaskStatusSucceeded {
 | 
						|
		return fmt.Errorf("DelDir failed, task status is %s", taskStatus)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) Release(ctx context.Context) error {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) Clear(ctx context.Context) error {
 | 
						|
	_, err := m.Client.Index(m.IndexUid).DeleteAllDocuments()
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func (m *Meilisearch) getTaskStatus(ctx context.Context, taskUID int64) (meilisearch.TaskStatus, error) {
 | 
						|
	forTask, err := m.Client.WaitForTask(taskUID, meilisearch.WaitParams{
 | 
						|
		Context:  ctx,
 | 
						|
		Interval: time.Second,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return meilisearch.TaskStatusUnknown, err
 | 
						|
	}
 | 
						|
	return forTask.Status, nil
 | 
						|
}
 |