186 lines
5.1 KiB
Go
186 lines
5.1 KiB
Go
package subsonic
|
|
|
|
import (
|
|
"log"
|
|
"math/rand"
|
|
"slices"
|
|
|
|
"golang.org/x/text/collate"
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/dweymouth/go-subsonic/subsonic"
|
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
|
"github.com/dweymouth/supersonic/backend/mediaprovider/helpers"
|
|
"github.com/dweymouth/supersonic/sharedutil"
|
|
)
|
|
|
|
func (s *subsonicMediaProvider) ArtistSortOrders() []string {
|
|
return []string{
|
|
mediaprovider.ArtistSortAlbumCount,
|
|
mediaprovider.ArtistSortNameAZ,
|
|
mediaprovider.ArtistSortRandom,
|
|
}
|
|
}
|
|
|
|
func filterArtistMatches(f mediaprovider.ArtistFilter, artist *subsonic.ArtistID3) bool {
|
|
if artist == nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *subsonicMediaProvider) IterateArtists(sortOrder string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
|
if sortOrder == "" {
|
|
sortOrder = mediaprovider.ArtistSortNameAZ // default
|
|
}
|
|
switch sortOrder {
|
|
case mediaprovider.ArtistSortAlbumCount:
|
|
return s.baseArtistIterFromSimpleSortOrder(
|
|
func(artists []*subsonic.ArtistID3) []*subsonic.ArtistID3 {
|
|
slices.SortStableFunc(artists, func(a, b *subsonic.ArtistID3) int {
|
|
return b.AlbumCount - a.AlbumCount
|
|
})
|
|
return artists
|
|
},
|
|
filter,
|
|
)
|
|
case mediaprovider.ArtistSortNameAZ:
|
|
return s.baseArtistIterFromSimpleSortOrder(
|
|
func(artists []*subsonic.ArtistID3) []*subsonic.ArtistID3 {
|
|
c := collate.New(language.English, collate.Loose)
|
|
slices.SortFunc(artists, func(a, b *subsonic.ArtistID3) int {
|
|
return c.CompareString(a.Name, b.Name)
|
|
})
|
|
return artists
|
|
},
|
|
filter,
|
|
)
|
|
case mediaprovider.ArtistSortRandom:
|
|
return s.baseArtistIterFromSimpleSortOrder(
|
|
func(artists []*subsonic.ArtistID3) []*subsonic.ArtistID3 {
|
|
newArtists := make([]*subsonic.ArtistID3, len(artists))
|
|
copy(newArtists, artists)
|
|
rand.Shuffle(len(newArtists), func(i, j int) { newArtists[i], newArtists[j] = newArtists[j], newArtists[i] })
|
|
return newArtists
|
|
},
|
|
filter,
|
|
)
|
|
default:
|
|
log.Printf("Undefined artist sort order: %s", sortOrder)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *subsonicMediaProvider) SearchArtists(searchQuery string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
|
return s.newSearchArtistIter(searchQuery, filter, s.prefetchCoverCB)
|
|
}
|
|
|
|
type searchArtistIter struct {
|
|
searchIterBase
|
|
|
|
prefetchCB func(string)
|
|
filter mediaprovider.ArtistFilter
|
|
prefetched []*subsonic.ArtistID3
|
|
prefetchedPos int
|
|
artistIDset map[string]bool
|
|
done bool
|
|
}
|
|
|
|
func (s *subsonicMediaProvider) newSearchArtistIter(query string, filter mediaprovider.ArtistFilter, cb func(string)) *searchArtistIter {
|
|
return &searchArtistIter{
|
|
searchIterBase: searchIterBase{
|
|
query: query,
|
|
s: s.client,
|
|
},
|
|
prefetchCB: cb,
|
|
filter: filter,
|
|
artistIDset: make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
func (s *searchArtistIter) Next() *mediaprovider.Artist {
|
|
if s.done {
|
|
return nil
|
|
}
|
|
|
|
// prefetch more search results from server
|
|
if s.prefetched == nil {
|
|
results := s.searchIterBase.fetchResults()
|
|
if results == nil {
|
|
s.done = true
|
|
s.artistIDset = nil
|
|
return nil
|
|
}
|
|
|
|
// add results from artists search
|
|
s.addNewArtists(results.Artist)
|
|
s.artistOffset += len(results.Artist)
|
|
}
|
|
|
|
// return from prefetched results
|
|
if len(s.prefetched) > 0 {
|
|
a := s.prefetched[s.prefetchedPos]
|
|
s.prefetchedPos++
|
|
if s.prefetchedPos == len(s.prefetched) {
|
|
s.prefetched = nil
|
|
s.prefetchedPos = 0
|
|
}
|
|
|
|
return toArtistFromID3(a)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *searchArtistIter) addNewArtists(artists []*subsonic.ArtistID3) {
|
|
for _, artist := range artists {
|
|
if _, have := s.artistIDset[artist.ID]; have {
|
|
continue
|
|
}
|
|
if !filterArtistMatches(s.filter, artist) {
|
|
continue
|
|
}
|
|
s.prefetched = append(s.prefetched, artist)
|
|
if s.prefetchCB != nil {
|
|
go s.prefetchCB(artist.CoverArt)
|
|
}
|
|
s.artistIDset[artist.ID] = true
|
|
}
|
|
}
|
|
|
|
func (s *subsonicMediaProvider) baseArtistIterFromSimpleSortOrder(sortFn func([]*subsonic.ArtistID3) []*subsonic.ArtistID3, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
|
return helpers.NewArtistIterator(s.artistFetchFnFromStandardSort(sortFn), filter, s.prefetchCoverCB)
|
|
}
|
|
|
|
func (s *subsonicMediaProvider) artistFetchFnFromStandardSort(sortFn func([]*subsonic.ArtistID3) []*subsonic.ArtistID3) helpers.ArtistFetchFn {
|
|
return makeArtistFetchFn(func(offset, limit int) ([]*subsonic.ArtistID3, error) {
|
|
// When the iterator asks for a second page of results, return nil, as Subsonic does not support pagination for artists.
|
|
if offset > 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
idxs, err := s.client.GetArtists(map[string]string{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var artists []*subsonic.ArtistID3
|
|
for _, idx := range idxs.Index {
|
|
for _, ar := range idx.Artist {
|
|
artists = append(artists, ar)
|
|
}
|
|
}
|
|
artists = sortFn(artists)
|
|
return artists, nil
|
|
})
|
|
}
|
|
|
|
func makeArtistFetchFn(subsonicFetchFn func(offset, limit int) ([]*subsonic.ArtistID3, error)) helpers.ArtistFetchFn {
|
|
return func(offset, limit int) ([]*mediaprovider.Artist, error) {
|
|
ar, err := subsonicFetchFn(offset, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sharedutil.MapSlice(ar, toArtistFromID3), nil
|
|
}
|
|
}
|