package persistence import ( "encoding/json" "fmt" "github.com/meilisearch/meilisearch-go" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" ) type MeilisearchService struct { client meilisearch.ServiceManager } func NewMeilisearchService() *MeilisearchService { if !conf.Server.Meilisearch.Enabled { return nil } client := meilisearch.New(conf.Server.Meilisearch.Host, meilisearch.WithAPIKey(conf.Server.Meilisearch.ApiKey)) return &MeilisearchService{client: client} } func (s *MeilisearchService) IndexMediaFile(mf *model.MediaFile) { if s == nil { return } s.IndexMediaFiles([]model.MediaFile{*mf}) } func (s *MeilisearchService) IndexMediaFiles(mfs []model.MediaFile) { if s == nil || len(mfs) == 0 { return } docs := make([]map[string]interface{}, len(mfs)) for i, mf := range mfs { docs[i] = map[string]interface{}{ "id": mf.ID, "title": mf.Title, "artist": mf.Artist, "album": mf.Album, "albumArtist": mf.AlbumArtist, "path": mf.Path, "year": mf.Year, "genre": mf.Genre, } } _, err := s.client.Index("mediafiles").AddDocuments(docs, nil) if err != nil { log.Error("Error indexing mediafiles", "count", len(mfs), err) } } func (s *MeilisearchService) DeleteMediaFile(id string) { if s == nil { return } _, err := s.client.Index("mediafiles").DeleteDocument(id) if err != nil { log.Error("Error deleting mediafile from index", "id", id, err) } } func (s *MeilisearchService) DeleteMediaFiles(ids []string) { if s == nil { return } _, err := s.client.Index("mediafiles").DeleteDocuments(ids) if err != nil { log.Error("Error deleting mediafiles from index", "ids", ids, err) } } func (s *MeilisearchService) IndexAlbum(album *model.Album) { if s == nil { return } s.IndexAlbums([]model.Album{*album}) } func (s *MeilisearchService) IndexAlbums(albums []model.Album) { if s == nil || len(albums) == 0 { return } docs := make([]map[string]interface{}, len(albums)) for i, album := range albums { docs[i] = map[string]interface{}{ "id": album.ID, "name": album.Name, "artist": album.AlbumArtist, "albumArtist": album.AlbumArtist, "year": album.MinYear, "genre": album.Genre, } } _, err := s.client.Index("albums").AddDocuments(docs, nil) if err != nil { log.Error("Error indexing albums", "count", len(albums), err) } } func (s *MeilisearchService) DeleteAlbum(id string) { if s == nil { return } _, err := s.client.Index("albums").DeleteDocument(id) if err != nil { log.Error("Error deleting album from index", "id", id, err) } } func (s *MeilisearchService) DeleteAlbums(ids []string) { if s == nil { return } _, err := s.client.Index("albums").DeleteDocuments(ids) if err != nil { log.Error("Error deleting albums from index", "ids", ids, err) } } func (s *MeilisearchService) IndexArtist(artist *model.Artist) { if s == nil { return } s.IndexArtists([]model.Artist{*artist}) } func (s *MeilisearchService) IndexArtists(artists []model.Artist) { if s == nil || len(artists) == 0 { return } docs := make([]map[string]interface{}, len(artists)) for i, artist := range artists { docs[i] = map[string]interface{}{ "id": artist.ID, "name": artist.Name, } } _, err := s.client.Index("artists").AddDocuments(docs, nil) if err != nil { log.Error("Error indexing artists", "count", len(artists), err) } } func (s *MeilisearchService) DeleteArtist(id string) { if s == nil { return } _, err := s.client.Index("artists").DeleteDocument(id) if err != nil { log.Error("Error deleting artist from index", "id", id, err) } } func (s *MeilisearchService) DeleteArtists(ids []string) { if s == nil { return } _, err := s.client.Index("artists").DeleteDocuments(ids) if err != nil { log.Error("Error deleting artists from index", "ids", ids, err) } } func (s *MeilisearchService) Search(indexName string, query string, offset, limit int) ([]string, error) { if s == nil { return nil, fmt.Errorf("meilisearch is not enabled") } searchRes, err := s.client.Index(indexName).Search(query, &meilisearch.SearchRequest{ Offset: int64(offset), Limit: int64(limit), }) if err != nil { return nil, err } var ids []string for _, hit := range searchRes.Hits { if id, ok := hit["id"]; ok { var idStr string if err := json.Unmarshal(id, &idStr); err == nil { ids = append(ids, idStr) } } } // Handle case where id might be non-string if necessary, though simpler is better for now. // Meilisearch returns map[string]interface{} return ids, nil }