navidrome/server/subsonic/searching.go

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

141 lines
4.1 KiB
Go
Raw Normal View History

package subsonic
2016-03-11 06:37:07 +01:00
import (
"context"
2016-03-24 02:06:39 +01:00
"fmt"
"net/http"
"reflect"
2020-08-14 04:27:37 +02:00
"strings"
"sync"
"time"
2016-03-24 02:06:39 +01:00
2023-01-12 19:39:05 +01:00
"github.com/deluan/sanitize"
2020-01-24 01:44:08 +01:00
"github.com/navidrome/navidrome/log"
2020-08-14 04:27:37 +02:00
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/server/public"
2020-01-24 01:44:08 +01:00
"github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils"
2016-03-11 06:37:07 +01:00
)
2020-01-11 18:04:40 +01:00
type searchParams struct {
2016-03-28 16:01:43 +02:00
query string
artistCount int
artistOffset int
albumCount int
albumOffset int
songCount int
songOffset int
2016-03-11 06:37:07 +01:00
}
func (api *Router) getParams(r *http.Request) (*searchParams, error) {
var err error
2020-01-11 18:04:40 +01:00
sp := &searchParams{}
2020-10-27 20:23:29 +01:00
sp.query, err = requiredParamString(r, "query")
if err != nil {
2020-01-11 18:04:40 +01:00
return nil, err
}
sp.artistCount = utils.ParamInt(r, "artistCount", 20)
sp.artistOffset = utils.ParamInt(r, "artistOffset", 0)
sp.albumCount = utils.ParamInt(r, "albumCount", 20)
sp.albumOffset = utils.ParamInt(r, "albumOffset", 0)
sp.songCount = utils.ParamInt(r, "songCount", 20)
sp.songOffset = utils.ParamInt(r, "songOffset", 0)
2020-01-11 18:04:40 +01:00
return sp, nil
2016-03-28 16:01:43 +02:00
}
type searchFunc[T any] func(q string, offset int, size int) (T, error)
2020-08-14 04:27:37 +02:00
2023-11-22 03:11:45 +01:00
func callSearch[T any](ctx context.Context, wg *sync.WaitGroup, s searchFunc[T], q string, offset, size int, result *T) {
defer wg.Done()
if size == 0 {
return
2016-03-11 16:03:33 +01:00
}
done := make(chan struct{})
go func() {
typ := strings.TrimPrefix(reflect.TypeOf(*result).String(), "model.")
var err error
start := time.Now()
*result, err = s(q, offset, size)
if err != nil {
2022-07-27 20:59:01 +02:00
log.Error(ctx, "Error searching "+typ, "query", q, err)
} else {
log.Trace(ctx, "Search for "+typ+" completed", "query", q, "elapsed", time.Since(start))
}
done <- struct{}{}
}()
select {
case <-done:
case <-ctx.Done():
2016-03-11 16:03:33 +01:00
}
}
func (api *Router) searchAll(ctx context.Context, sp *searchParams) (mediaFiles model.MediaFiles, albums model.Albums, artists model.Artists) {
start := time.Now()
q := sanitize.Accents(strings.ToLower(strings.TrimSuffix(sp.query, "*")))
wg := &sync.WaitGroup{}
wg.Add(3)
2023-11-22 03:11:45 +01:00
go callSearch(ctx, wg, api.ds.MediaFile(ctx).Search, q, sp.songOffset, sp.songCount, &mediaFiles)
go callSearch(ctx, wg, api.ds.Album(ctx).Search, q, sp.albumOffset, sp.albumCount, &albums)
go callSearch(ctx, wg, api.ds.Artist(ctx).Search, q, sp.artistOffset, sp.artistCount, &artists)
wg.Wait()
2016-03-11 06:37:07 +01:00
if ctx.Err() == nil {
log.Debug(ctx, fmt.Sprintf("Search resulted in %d songs, %d albums and %d artists",
len(mediaFiles), len(albums), len(artists)), "query", sp.query, "elapsedTime", time.Since(start))
} else {
log.Warn(ctx, "Search was interrupted", ctx.Err(), "query", sp.query, "elapsedTime", time.Since(start))
}
2020-08-14 04:27:37 +02:00
return mediaFiles, albums, artists
2016-03-28 16:01:43 +02:00
}
func (api *Router) Search2(r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
sp, err := api.getParams(r)
if err != nil {
return nil, err
}
mfs, als, as := api.searchAll(ctx, sp)
2016-03-24 02:06:39 +01:00
response := newResponse()
2016-03-11 06:37:07 +01:00
searchResult2 := &responses.SearchResult2{}
2020-08-14 04:27:37 +02:00
searchResult2.Artist = make([]responses.Artist, len(as))
for i, artist := range as {
2022-10-01 00:23:47 +02:00
artist := artist
2020-08-14 04:27:37 +02:00
searchResult2.Artist[i] = responses.Artist{
Id: artist.ID,
Name: artist.Name,
AlbumCount: int32(artist.AlbumCount),
UserRating: int32(artist.Rating),
CoverArt: artist.CoverArtID().String(),
ArtistImageUrl: public.ImageURL(r, artist.CoverArtID(), 600),
2020-08-14 04:27:37 +02:00
}
if artist.Starred {
2022-10-01 00:23:47 +02:00
searchResult2.Artist[i].Starred = &as[i].StarredAt
2020-08-14 04:27:37 +02:00
}
}
searchResult2.Album = childrenFromAlbums(ctx, als)
searchResult2.Song = childrenFromMediaFiles(ctx, mfs)
2016-03-11 06:37:07 +01:00
response.SearchResult2 = searchResult2
return response, nil
2016-03-11 06:37:07 +01:00
}
2016-03-28 16:01:43 +02:00
func (api *Router) Search3(r *http.Request) (*responses.Subsonic, error) {
ctx := r.Context()
sp, err := api.getParams(r)
if err != nil {
return nil, err
}
mfs, als, as := api.searchAll(ctx, sp)
2016-03-28 16:01:43 +02:00
response := newResponse()
2016-03-28 16:01:43 +02:00
searchResult3 := &responses.SearchResult3{}
searchResult3.Artist = make([]responses.ArtistID3, len(as))
2020-08-14 04:27:37 +02:00
for i, artist := range as {
searchResult3.Artist[i] = toArtistID3(r, artist)
2016-03-28 16:01:43 +02:00
}
searchResult3.Album = childrenFromAlbums(ctx, als)
searchResult3.Song = childrenFromMediaFiles(ctx, mfs)
2016-03-28 16:01:43 +02:00
response.SearchResult3 = searchResult3
return response, nil
2016-03-28 16:01:43 +02:00
}