diff --git a/backend/albumiterator.go b/backend/albumiterator.go index d1d7cfa..ab47e6b 100644 --- a/backend/albumiterator.go +++ b/backend/albumiterator.go @@ -3,8 +3,10 @@ package backend import ( "log" "strconv" + "strings" "github.com/dweymouth/go-subsonic/subsonic" + "github.com/dweymouth/supersonic/sharedutil" ) type AlbumSortOrder string @@ -33,36 +35,69 @@ var ( } ) -func (l *LibraryManager) AlbumsIter(sort AlbumSortOrder) AlbumIterator { +type AlbumFilter struct { + MinYear int + MaxYear int // 0 == unset/match any + Genres []string // len(0) == unset/match any + + ExcludeFavorited bool // mut. exc. with ExcludeUnfavorited + ExcludeUnfavorited bool // mut. exc. with ExcludeFavorited +} + +func (f *AlbumFilter) Matches(album *subsonic.AlbumID3) bool { + if album == nil { + return false + } + if f.ExcludeFavorited && !album.Starred.IsZero() { + return false + } + if f.ExcludeUnfavorited && album.Starred.IsZero() { + return false + } + if y := album.Year; y < f.MinYear || (f.MaxYear > 0 && y > f.MaxYear) { + return false + } + if len(f.Genres) == 0 { + return true + } + for _, g := range f.Genres { + if strings.EqualFold(g, album.Genre) { + return true + } + } + return false +} + +func (l *LibraryManager) AlbumsIter(sort AlbumSortOrder, filter AlbumFilter) AlbumIterator { switch sort { case AlbumSortRecentlyAdded: - return l.newBaseIter("newest", make(map[string]string)) + return l.newBaseIter("newest", filter, make(map[string]string)) case AlbumSortRecentlyPlayed: - return l.newBaseIter("recent", make(map[string]string)) + return l.newBaseIter("recent", filter, make(map[string]string)) case AlbumSortFrequentlyPlayed: - return l.newBaseIter("frequent", make(map[string]string)) + return l.newBaseIter("frequent", filter, make(map[string]string)) case AlbumSortRandom: return l.newRandomIter() case AlbumSortTitleAZ: - return l.newBaseIter("alphabeticalByName", make(map[string]string)) + return l.newBaseIter("alphabeticalByName", filter, make(map[string]string)) case AlbumSortArtistAZ: - return l.newBaseIter("alphabeticalByArtist", make(map[string]string)) + return l.newBaseIter("alphabeticalByArtist", filter, make(map[string]string)) case AlbumSortYearAscending: - return l.newBaseIter("byYear", map[string]string{"fromYear": "0", "toYear": "3000"}) + return l.newBaseIter("byYear", filter, map[string]string{"fromYear": "0", "toYear": "3000"}) case AlbumSortYearDescending: - return l.newBaseIter("byYear", map[string]string{"fromYear": "3000", "toYear": "0"}) + return l.newBaseIter("byYear", filter, map[string]string{"fromYear": "3000", "toYear": "0"}) default: log.Printf("Undefined album sort order: %s", sort) return nil } } -func (l *LibraryManager) StarredIter() AlbumIterator { - return l.newBaseIter("starred", make(map[string]string)) +func (l *LibraryManager) StarredIter(filter AlbumFilter) AlbumIterator { + return l.newBaseIter("starred", filter, make(map[string]string)) } -func (l *LibraryManager) GenreIter(genre string) AlbumIterator { - return l.newBaseIter("byGenre", map[string]string{"genre": genre}) +func (l *LibraryManager) GenreIter(genre string, filter AlbumFilter) AlbumIterator { + return l.newBaseIter("byGenre", filter, map[string]string{"genre": genre}) } func (l *LibraryManager) SearchIter(query string) AlbumIterator { @@ -83,6 +118,7 @@ func (l *LibraryManager) GetAlbum(id string) (*subsonic.AlbumID3, error) { type baseIter struct { listType string + filter AlbumFilter pos int l *LibraryManager s *subsonic.Client @@ -92,9 +128,10 @@ type baseIter struct { done bool } -func (l *LibraryManager) newBaseIter(listType string, opts map[string]string) *baseIter { +func (l *LibraryManager) newBaseIter(listType string, filter AlbumFilter, opts map[string]string) *baseIter { return &baseIter{ listType: listType, + filter: filter, l: l, s: l.s.Server, opts: opts, @@ -123,6 +160,7 @@ func (r *baseIter) Next() *subsonic.AlbumID3 { log.Println(err) albums = nil } + albums = sharedutil.FilterSlice(albums, r.filter.Matches) if len(albums) == 0 { r.done = true return nil diff --git a/backend/trackiterator.go b/backend/trackiterator.go index 02ccefa..ef9fc98 100644 --- a/backend/trackiterator.go +++ b/backend/trackiterator.go @@ -9,7 +9,7 @@ import ( func (l *LibraryManager) AllTracksIterator() TrackIterator { return &allTracksIterator{ l: l, - albumIter: l.AlbumsIter(AlbumSortArtistAZ), + albumIter: l.AlbumsIter(AlbumSortArtistAZ, AlbumFilter{}), } } diff --git a/sharedutil/sharedutil.go b/sharedutil/sharedutil.go index 8502cde..d07098c 100644 --- a/sharedutil/sharedutil.go +++ b/sharedutil/sharedutil.go @@ -17,6 +17,9 @@ func SliceContains[T comparable](ts []T, t T) bool { } func FilterSlice[T any](ss []T, test func(T) bool) []T { + if ss == nil { + return nil + } result := make([]T, 0) for _, s := range ss { if test(s) { @@ -27,6 +30,9 @@ func FilterSlice[T any](ss []T, test func(T) bool) []T { } func MapSlice[T any, U any](ts []T, f func(T) U) []U { + if ts == nil { + return nil + } result := make([]U, len(ts)) for i, t := range ts { result[i] = f(t) diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 5faf155..293851a 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -71,7 +71,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, cfg.SortOrder = string(backend.AlbumSortRecentlyAdded) } a.sortOrder.Selected = cfg.SortOrder - iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)) + iter := lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), backend.AlbumFilter{}) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im) contr.ConnectAlbumGridActions(a.grid) a.searcher = widgets.NewSearchEntry() @@ -154,7 +154,7 @@ func (a *AlbumsPage) Reload() { if a.searchText != "" { a.doSearch(a.searchText) } else { - iter := a.lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected)) + iter := a.lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), backend.AlbumFilter{}) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) a.grid.Refresh() } @@ -190,7 +190,7 @@ func (a *AlbumsPage) doSearch(query string) { func (a *AlbumsPage) onSortOrderChanged(order string) { a.cfg.SortOrder = a.sortOrder.Selected - iter := a.lm.AlbumsIter(backend.AlbumSortOrder(order)) + iter := a.lm.AlbumsIter(backend.AlbumSortOrder(order), backend.AlbumFilter{}) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) if a.searchText == "" { a.container.Objects[0] = a.grid diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index ee398b6..ccb0eed 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -55,7 +55,8 @@ func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Contro } a.ExtendBaseWidget(a) a.createHeader(0, "") - a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(lm.StarredIter()), a.im) + iter := lm.StarredIter(backend.AlbumFilter{}) + a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) a.contr.ConnectAlbumGridActions(a.grid) if cfg.InitialView == "Artists" { a.toggleBtns.SetActivatedButton(1) @@ -129,7 +130,8 @@ func (a *FavoritesPage) Reload() { if a.searchText != "" { a.doSearchAlbums(a.searchText) } else { - a.grid.Reset(widgets.NewGridViewAlbumIterator(a.lm.StarredIter())) + iter := a.lm.StarredIter(backend.AlbumFilter{}) + a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) } if a.tracklistCtr != nil || a.artistListCtr != nil { go func() { diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index 0b8ee76..6e19da5 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -52,7 +52,7 @@ func NewGenrePage(genre string, contr *controller.Controller, pm *backend.Playba SizeName: theme.SizeNameHeadingText, } g.playRandom = widget.NewButtonWithIcon(" Play random", myTheme.ShuffleIcon, g.playRandomSongs) - iter := g.lm.GenreIter(g.genre) + iter := g.lm.GenreIter(g.genre, backend.AlbumFilter{}) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.contr.ConnectAlbumGridActions(g.grid) g.searcher = widgets.NewSearchEntry() @@ -118,7 +118,8 @@ func (g *GenrePage) Reload() { if g.searchText != "" { g.doSearch(g.searchText) } else { - g.grid.Reset(widgets.NewGridViewAlbumIterator(g.lm.GenreIter(g.genre))) + iter := g.lm.GenreIter(g.genre, backend.AlbumFilter{}) + g.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) g.grid.Refresh() } }