add SearchAll function to MediaProvider

This commit is contained in:
Drew Weymouth
2023-11-05 14:07:52 -08:00
parent 9f63822806
commit 0439ec695b
4 changed files with 203 additions and 3 deletions
+2
View File
@@ -57,6 +57,8 @@ type MediaProvider interface {
SearchAlbums(searchQuery string, filter AlbumFilter) AlbumIterator
SearchAll(searchQuery string, maxResults int) ([]*SearchResult, error)
GetRandomTracks(genre string, count int) ([]*Track, error)
GetSimilarTracks(artistID string, count int) ([]*Track, error)
+42
View File
@@ -87,3 +87,45 @@ type PlaylistWithTracks struct {
Playlist
Tracks []*Track
}
type ContentType int
const (
ContentTypeAlbum ContentType = iota
ContentTypeArtist
ContentTypeTrack
ContentTypePlaylist
ContentTypeGenre
)
func (c ContentType) String() string {
switch c {
case ContentTypeAlbum:
return "Album"
case ContentTypeArtist:
return "Artist"
case ContentTypeTrack:
return "Track"
case ContentTypePlaylist:
return "Playlist"
case ContentTypeGenre:
return "Genre"
default:
return "Unknown"
}
}
type SearchResult struct {
Name string
ID string
CoverID string
Type ContentType
// for Album / Playlist: track count
// Artist / Genre: album count
// Track: length (seconds)
Size int
// Unset for ContentTypes Artist, Playlist, and Genre
ArtistName string
}
+136
View File
@@ -0,0 +1,136 @@
package subsonic
import (
"sort"
"strconv"
"strings"
"sync"
"github.com/dweymouth/go-subsonic/subsonic"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil"
)
func (s *subsonicMediaProvider) SearchAll(searchQuery string, maxResults int) ([]*mediaprovider.SearchResult, error) {
var wg sync.WaitGroup
var err error // only set by Search3
var result *subsonic.SearchResult3
var playlists []*subsonic.Playlist
var genres []*subsonic.Genre
wg.Add(1)
go func() {
count := strconv.Itoa(maxResults / 3)
res, e := s.client.Search3(searchQuery, map[string]string{
"artistCount": count,
"albumCount": count,
"songCount": count,
})
if e != nil {
err = e
} else {
result = res
}
wg.Done()
}()
queryLowerWords := strings.Fields(strings.ToLower(searchQuery))
wg.Add(1)
go func() {
p, e := s.client.GetPlaylists(nil)
if e != nil {
playlists = sharedutil.FilterSlice(p, func(p *subsonic.Playlist) bool {
return allTermsMatch(strings.ToLower(p.Name), queryLowerWords)
})
}
wg.Done()
}()
wg.Add(1)
go func() {
g, e := s.client.GetGenres()
if e != nil {
genres = sharedutil.FilterSlice(g, func(g *subsonic.Genre) bool {
return allTermsMatch(strings.ToLower(g.Name), queryLowerWords)
})
}
wg.Done()
}()
wg.Wait()
if err != nil {
return nil, err
}
results := mergeResults(result, playlists, genres)
//rankResults(results, queryLowerWords) // TODO
if len(results) > maxResults {
results = results[:maxResults]
}
return results, nil
}
// name and terms should be pre-converted to the same case
func allTermsMatch(name string, terms []string) bool {
for _, t := range terms {
if !strings.Contains(name, t) {
return false
}
}
return true
}
func mergeResults(
searchResult *subsonic.SearchResult3,
matchingPlaylists []*subsonic.Playlist,
matchingGenres []*subsonic.Genre,
) []*mediaprovider.SearchResult {
var results []*mediaprovider.SearchResult
for _, al := range searchResult.Album {
results = append(results, &mediaprovider.SearchResult{
Type: mediaprovider.ContentTypeAlbum,
Name: al.Name,
ArtistName: getNameString(al.Artist, al.Artists),
Size: al.SongCount,
})
}
for _, ar := range searchResult.Artist {
results = append(results, &mediaprovider.SearchResult{
Type: mediaprovider.ContentTypeArtist,
Name: ar.Name,
Size: ar.AlbumCount,
})
}
for _, tr := range searchResult.Song {
results = append(results, &mediaprovider.SearchResult{
Type: mediaprovider.ContentTypeTrack,
Name: tr.Title,
ArtistName: getNameString(tr.Artist, tr.Artists),
Size: tr.Duration,
})
}
return results
}
func rankResults(results []*mediaprovider.SearchResult, queryTerms []string) {
// TODO
sort.Slice(results, func(a, b int) bool {
return false
})
}
// select Subsonic single-valued name or join OpenSubsonic multi-valued names
func getNameString(singleName string, idNames []subsonic.IDName) string {
if len(idNames) == 0 {
return singleName
}
names := sharedutil.MapSlice(idNames, func(a subsonic.IDName) string {
return a.Name
})
return strings.Join(names, ", ")
}
@@ -14,9 +14,17 @@ import (
"github.com/dweymouth/supersonic/sharedutil"
)
const cacheValidDurationSeconds = 60
type subsonicMediaProvider struct {
client *subsonic.Client
prefetchCoverCB func(coverArtID string)
genresCached []*mediaprovider.Genre
genresCachedAt int64 // unix
playlistsCached []*mediaprovider.Playlist
playlistsCachedAt int64 // unix
}
func SubsonicMediaProvider(subsonicClient *subsonic.Client) mediaprovider.MediaProvider {
@@ -139,17 +147,23 @@ func (s *subsonicMediaProvider) GetFavorites() (mediaprovider.Favorites, error)
}
func (s *subsonicMediaProvider) GetGenres() ([]*mediaprovider.Genre, error) {
if s.genresCached != nil && time.Now().Unix()-s.genresCachedAt < cacheValidDurationSeconds {
return s.genresCached, nil
}
g, err := s.client.GetGenres()
if err != nil {
return nil, err
}
return sharedutil.MapSlice(g, func(g *subsonic.Genre) *mediaprovider.Genre {
s.genresCached = sharedutil.MapSlice(g, func(g *subsonic.Genre) *mediaprovider.Genre {
return &mediaprovider.Genre{
Name: g.Name,
AlbumCount: g.AlbumCount,
TrackCount: g.SongCount,
}
}), nil
})
s.genresCachedAt = time.Now().Unix()
return s.genresCached, nil
}
func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.PlaylistWithTracks, error) {
@@ -165,11 +179,17 @@ func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P
}
func (s *subsonicMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) {
if s.playlistsCached != nil && time.Now().Unix()-s.playlistsCachedAt < cacheValidDurationSeconds {
return s.playlistsCached, nil
}
pl, err := s.client.GetPlaylists(map[string]string{})
if err != nil {
return nil, err
}
return sharedutil.MapSlice(pl, toPlaylist), nil
s.playlistsCached = sharedutil.MapSlice(pl, toPlaylist)
s.playlistsCachedAt = time.Now().Unix()
return s.playlistsCached, nil
}
func (s *subsonicMediaProvider) GetRandomTracks(genreName string, count int) ([]*mediaprovider.Track, error) {