Merge pull request #355 from adamantike/misc/migrate-artists-page-to-gridview

misc: Migrate Artists page to GridView
This commit is contained in:
Drew Weymouth
2024-03-31 08:06:07 -07:00
committed by GitHub
12 changed files with 254 additions and 220 deletions
+8
View File
@@ -64,6 +64,10 @@ type ArtistPageConfig struct {
TracklistColumns []string
}
type ArtistsPageConfig struct {
SortOrder string
}
type FavoritesPageConfig struct {
InitialView string
TracklistColumns []string
@@ -122,6 +126,7 @@ type Config struct {
AlbumPage AlbumPageConfig
AlbumsPage AlbumsPageConfig
ArtistPage ArtistPageConfig
ArtistsPage ArtistsPageConfig
FavoritesPage FavoritesPageConfig
PlaylistPage PlaylistPageConfig
PlaylistsPage PlaylistsPageConfig
@@ -163,6 +168,9 @@ func DefaultConfig(appVersionTag string) *Config {
InitialView: "Discography",
TracklistColumns: []string{"Album", "Time", "Plays", "Favorite", "Rating"},
},
ArtistsPage: ArtistsPageConfig{
SortOrder: string("Name (A-Z)"),
},
FavoritesPage: FavoritesPageConfig{
TracklistColumns: []string{"Artist", "Album", "Time", "Plays"},
InitialView: "Albums",
+5 -4
View File
@@ -29,10 +29,11 @@ func NewAlbumIterator(fetchFn AlbumFetchFn, filter mediaprovider.AlbumFilter, cb
type ArtistFetchFn func(offset, limit int) ([]*mediaprovider.Artist, error)
func NewArtistIterator(fetchFn ArtistFetchFn) mediaprovider.ArtistIterator {
return &baseIter[mediaprovider.Artist, nilFilterOptions]{
fetcher: fetchFn,
filter: nilFilter[mediaprovider.Artist]{},
func NewArtistIterator(fetchFn ArtistFetchFn, filter mediaprovider.ArtistFilter, cb func(string)) mediaprovider.ArtistIterator {
return &baseIter[mediaprovider.Artist, mediaprovider.ArtistFilterOptions]{
prefetchCB: func(a *mediaprovider.Artist) { cb(a.CoverArtID) },
fetcher: fetchFn,
filter: filter,
}
}
+13 -2
View File
@@ -124,7 +124,7 @@ func (j *jellyfinMediaProvider) IterateTracks(searchQuery string) mediaprovider.
return helpers.NewTrackIterator(fetcher, j.prefetchCoverCB)
}
func (j *jellyfinMediaProvider) IterateArtists(sortOrder string) mediaprovider.ArtistIterator {
func (j *jellyfinMediaProvider) IterateArtists(sortOrder string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
var jfSort jellyfin.Sort
if sortOrder == "" {
@@ -147,7 +147,18 @@ func (j *jellyfinMediaProvider) IterateArtists(sortOrder string) mediaprovider.A
return sharedutil.MapSlice(ar, toArtist), nil
}
return helpers.NewArtistIterator(fetcher)
return helpers.NewArtistIterator(fetcher, filter, j.prefetchCoverCB)
}
func (j *jellyfinMediaProvider) SearchArtists(searchQuery string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
fetcher := func(offs, limit int) ([]*mediaprovider.Artist, error) {
sr, err := j.client.Search(searchQuery, jellyfin.TypeArtist, jellyfin.Paging{StartIndex: offs, Limit: limit})
if err != nil {
return nil, err
}
return sharedutil.MapSlice(sr.Artists, toArtist), nil
}
return helpers.NewArtistIterator(fetcher, filter, j.prefetchCoverCB)
}
// Creates the Jellyfin filter to implement the given mediaprovider filter,
+45 -1
View File
@@ -94,6 +94,48 @@ func (f albumFilter) Matches(album *Album) bool {
return genresMatch(f.options.Genres, album.Genres)
}
type ArtistFilter = MediaFilter[Artist, ArtistFilterOptions]
type ArtistFilterOptions struct{}
// Clone returns a deep copy of the filter options
func (o ArtistFilterOptions) Clone() ArtistFilterOptions {
return ArtistFilterOptions{}
}
type artistFilter struct {
options ArtistFilterOptions
}
func NewArtistFilter(options ArtistFilterOptions) *artistFilter {
return &artistFilter{options}
}
func (a artistFilter) Options() ArtistFilterOptions {
return a.options
}
func (a *artistFilter) SetOptions(o ArtistFilterOptions) {
a.options = o
}
// Clone returns a deep copy of the filter
func (a artistFilter) Clone() ArtistFilter {
return NewArtistFilter(a.options.Clone())
}
// Returns true if the filter is the nil filter - i.e. matches everything
func (a artistFilter) IsNil() bool {
return true
}
func (f artistFilter) Matches(artist *Artist) bool {
if artist == nil {
return false
}
return false
}
type RatingFavoriteParameters struct {
AlbumIDs []string
ArtistIDs []string
@@ -149,7 +191,9 @@ type MediaProvider interface {
ArtistSortOrders() []string
IterateArtists(sortOrder string) ArtistIterator
IterateArtists(sortOrder string, filter ArtistFilter) ArtistIterator
SearchArtists(searchQuery string, filter ArtistFilter) ArtistIterator
GetGenres() ([]*Genre, error)
@@ -23,7 +23,14 @@ func (s *subsonicMediaProvider) ArtistSortOrders() []string {
}
}
func (s *subsonicMediaProvider) IterateArtists(sortOrder string) mediaprovider.ArtistIterator {
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 = ArtistSortNameAZ // default
}
@@ -37,6 +44,7 @@ func (s *subsonicMediaProvider) IterateArtists(sortOrder string) mediaprovider.A
})
return artists
},
filter,
)
default:
log.Printf("Undefined artist sort order: %s", sortOrder)
@@ -44,8 +52,85 @@ func (s *subsonicMediaProvider) IterateArtists(sortOrder string) mediaprovider.A
}
}
func (s *subsonicMediaProvider) baseArtistIterFromSimpleSortOrder(sortFn func([]*subsonic.ArtistID3) []*subsonic.ArtistID3) mediaprovider.ArtistIterator {
return helpers.NewArtistIterator(s.artistFetchFnFromStandardSort(sortFn))
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 {
+10 -8
View File
@@ -164,20 +164,22 @@ func (s *ServerManager) connect(connection ServerConnection, password string) (m
if connection.ServerType == ServerTypeJellyfin {
client, err := jellyfin.NewClient(connection.Hostname, res.AppName, res.AppVersion, jellyfin.WithTimeout(10*time.Second))
if err != nil {
log.Print("Error creating Jellyfin client")
log.Printf("error creating Jellyfin client: %s", err.Error())
return nil, err
}
cli = &jellyfinMP.JellyfinServer{
Client: *client,
}
altClient, err := jellyfin.NewClient(connection.AltHostname, res.AppName, res.AppVersion, jellyfin.WithTimeout(10*time.Second))
if err != nil {
log.Print("Error creating Jellyfin alternative client")
return nil, err
}
altCli = &jellyfinMP.JellyfinServer{
Client: *altClient,
if connection.AltHostname != "" {
altClient, err := jellyfin.NewClient(connection.AltHostname, res.AppName, res.AppVersion, jellyfin.WithTimeout(10*time.Second))
if err != nil {
log.Printf("error creating Jellyfin alternative client: %s", err.Error())
return nil, err
}
altCli = &jellyfinMP.JellyfinServer{
Client: *altClient,
}
}
} else {
cli = &subsonicMP.SubsonicServer{