diff --git a/backend/mediaprovider/mediaprovider.go b/backend/mediaprovider/mediaprovider.go index 5839bdf..c8e6aaa 100644 --- a/backend/mediaprovider/mediaprovider.go +++ b/backend/mediaprovider/mediaprovider.go @@ -44,10 +44,12 @@ type MediaProvider interface { AlbumSortOrders() []string - IterateAlbums(sortOrder string, searchQuery string, filter AlbumFilter) AlbumIterator + IterateAlbums(sortOrder string, filter AlbumFilter) AlbumIterator IterateTracks(searchQuery string) TrackIterator + SearchAlbums(searchQuery string, filter AlbumFilter) AlbumIterator + GetRandomTracks(genre string, count int) ([]*Track, error) GetSimilarTracks(artistID string, count int) ([]*Track, error) diff --git a/backend/mediaprovider/subsonic/albumiterator.go b/backend/mediaprovider/subsonic/albumiterator.go index f198e1f..6ed86a2 100644 --- a/backend/mediaprovider/subsonic/albumiterator.go +++ b/backend/mediaprovider/subsonic/albumiterator.go @@ -10,6 +10,30 @@ import ( "github.com/dweymouth/supersonic/sharedutil" ) +const ( + AlbumSortRecentlyAdded string = "Recently Added" + AlbumSortRecentlyPlayed string = "Recently Played" + AlbumSortFrequentlyPlayed string = "Frequently Played" + AlbumSortRandom string = "Random" + AlbumSortTitleAZ string = "Title (A-Z)" + AlbumSortArtistAZ string = "Artist (A-Z)" + AlbumSortYearAscending string = "Year (ascending)" + AlbumSortYearDescending string = "Year (descending)" +) + +func (s *subsonicMediaProvider) AlbumSortOrders() []string { + return []string{ + AlbumSortRecentlyAdded, + AlbumSortRecentlyPlayed, + AlbumSortFrequentlyPlayed, + AlbumSortRandom, + AlbumSortTitleAZ, + AlbumSortArtistAZ, + AlbumSortYearAscending, + AlbumSortYearDescending, + } +} + func filterMatches(f mediaprovider.AlbumFilter, album *subsonic.AlbumID3) bool { if album == nil { return false @@ -34,15 +58,7 @@ func filterMatches(f mediaprovider.AlbumFilter, album *subsonic.AlbumID3) bool { return false } -func filterIsEmpty(f mediaprovider.AlbumFilter) bool { - return !f.ExcludeFavorited && !f.ExcludeUnfavorited && - f.MinYear == 0 && f.MaxYear == 0 && len(f.Genres) == 0 -} - -func (s *subsonicMediaProvider) IterateAlbums(sortOrder, searchQuery string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator { - if searchQuery != "" { - return s.newSearchIter(searchQuery, filter) - } +func (s *subsonicMediaProvider) IterateAlbums(sortOrder string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator { if sortOrder == "" && len(filter.Genres) == 1 { return s.newBaseIter("byGenre", filter, map[string]string{"genre": filter.Genres[0]}) } @@ -72,6 +88,10 @@ func (s *subsonicMediaProvider) IterateAlbums(sortOrder, searchQuery string, fil } } +func (s *subsonicMediaProvider) SearchAlbums(searchQuery string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator { + return s.newSearchIter(searchQuery, filter) +} + type baseIter struct { listType string filter mediaprovider.AlbumFilter @@ -217,7 +237,7 @@ func (s *searchIter) addNewAlbums(al []*subsonic.AlbumID3) { if _, have := s.albumIDset[album.ID]; have { continue } - if filterMatches(s.filter, album) { + if !filterMatches(s.filter, album) { continue } s.prefetched = append(s.prefetched, album) @@ -326,27 +346,3 @@ func (r *randomIter) Next() *mediaprovider.Album { return nil } - -/* -type BatchingIterator struct { - iter AlbumIterator -} - -func NewBatchingIterator(iter AlbumIterator) *BatchingIterator { - return &BatchingIterator{iter} -} - -func (b *BatchingIterator) NextN(n int) []*subsonic.AlbumID3 { - results := make([]*subsonic.AlbumID3, 0, n) - i := 0 - for i < n { - album := b.iter.Next() - if album == nil { - break - } - results = append(results, album) - i++ - } - return results -} -*/ diff --git a/backend/mediaprovider/subsonic/subsonicmediaprovider.go b/backend/mediaprovider/subsonic/subsonicmediaprovider.go index 4eee99a..170da6f 100644 --- a/backend/mediaprovider/subsonic/subsonicmediaprovider.go +++ b/backend/mediaprovider/subsonic/subsonicmediaprovider.go @@ -12,17 +12,6 @@ import ( "github.com/dweymouth/supersonic/sharedutil" ) -const ( - AlbumSortRecentlyAdded string = "Recently Added" - AlbumSortRecentlyPlayed string = "Recently Played" - AlbumSortFrequentlyPlayed string = "Frequently Played" - AlbumSortRandom string = "Random" - AlbumSortTitleAZ string = "Title (A-Z)" - AlbumSortArtistAZ string = "Artist (A-Z)" - AlbumSortYearAscending string = "Year (ascending)" - AlbumSortYearDescending string = "Year (descending)" -) - type subsonicMediaProvider struct { client *subsonic.Client } @@ -31,19 +20,6 @@ func SubsonicMediaProvider(subsonicClient *subsonic.Client) mediaprovider.MediaP return &subsonicMediaProvider{client: subsonicClient} } -func (s *subsonicMediaProvider) AlbumSortOrders() []string { - return []string{ - AlbumSortRecentlyAdded, - AlbumSortRecentlyPlayed, - AlbumSortFrequentlyPlayed, - AlbumSortRandom, - AlbumSortTitleAZ, - AlbumSortArtistAZ, - AlbumSortYearAscending, - AlbumSortYearDescending, - } -} - func (s *subsonicMediaProvider) CreatePlaylist(name string, trackIDs []string) error { return s.client.CreatePlaylistWithTracks(trackIDs, map[string]string{"name": name}) } @@ -69,21 +45,11 @@ func (s *subsonicMediaProvider) GetAlbum(albumID string) (*mediaprovider.AlbumWi if err != nil { return nil, err } - return &mediaprovider.AlbumWithTracks{ - Album: mediaprovider.Album{ - ID: al.ID, - Name: al.Name, - ArtistIDs: []string{al.ArtistID}, - CoverArtID: al.CoverArt, - ArtistNames: []string{al.Artist}, - Genres: []string{al.Genre}, - Year: al.Year, - TrackCount: al.SongCount, - Favorite: !al.Starred.IsZero(), - Duration: al.Duration, - }, + album := &mediaprovider.AlbumWithTracks{ Tracks: sharedutil.MapSlice(al.Song, toTrack), - }, nil + } + fillAlbum(al, &album.Album) + return album, nil } func (s *subsonicMediaProvider) GetArtist(artistID string) (*mediaprovider.ArtistWithAlbums, error) { @@ -168,19 +134,11 @@ func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P if err != nil { return nil, err } - return &mediaprovider.PlaylistWithTracks{ - Playlist: mediaprovider.Playlist{ - ID: pl.ID, - CoverArtID: pl.CoverArt, - Name: pl.Name, - Description: pl.Comment, - TrackCount: pl.SongCount, - Public: pl.Public, - Owner: pl.Owner, - Duration: pl.Duration, - }, + playlist := &mediaprovider.PlaylistWithTracks{ Tracks: sharedutil.MapSlice(pl.Entry, toTrack), - }, nil + } + fillPlaylist(pl, &playlist.Playlist) + return playlist, nil } func (s *subsonicMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) { @@ -261,34 +219,24 @@ func (s *subsonicMediaProvider) SetRating(params mediaprovider.RatingFavoritePar var err error batchSetRating := func(offs int, wg *sync.WaitGroup) { for i := 0; i < batchSize && offs+i < len(params.TrackIDs); i++ { - if wg != nil { - wg.Add(1) - } + wg.Add(1) go func(idx int) { newErr := s.client.SetRating(params.TrackIDs[idx], rating) if err == nil && newErr != nil { err = newErr } - if wg != nil { - wg.Done() - } + wg.Done() }(offs + i) } } - if len(params.TrackIDs) <= 5 { - // one batch only - no need to use wait group - batchSetRating(0, nil) - } else { - go func() { - numBatches := int(math.Ceil(float64(len(params.TrackIDs)) / float64(batchSize))) - for i := 0; i < numBatches; i++ { - var wg sync.WaitGroup - batchSetRating(i*batchSize, &wg) - wg.Wait() - } - }() + numBatches := int(math.Ceil(float64(len(params.TrackIDs)) / float64(batchSize))) + for i := 0; i < numBatches; i++ { + var wg sync.WaitGroup + batchSetRating(i*batchSize, &wg) + wg.Wait() } + return err } @@ -314,6 +262,7 @@ func toTrack(ch *subsonic.Child) *mediaprovider.Track { Favorite: !ch.Starred.IsZero(), PlayCount: int(ch.PlayCount), FilePath: ch.Path, + Size: ch.Size, BitRate: ch.BitRate, } } @@ -322,18 +271,22 @@ func toAlbum(al *subsonic.AlbumID3) *mediaprovider.Album { if al == nil { return nil } - return &mediaprovider.Album{ - ID: al.ID, - CoverArtID: al.CoverArt, - Name: al.Name, - Duration: al.Duration, - ArtistIDs: []string{al.ArtistID}, - ArtistNames: []string{al.Artist}, - Year: al.Year, - Genres: []string{al.Genre}, - TrackCount: al.SongCount, - Favorite: !al.Starred.IsZero(), - } + album := &mediaprovider.Album{} + fillAlbum(al, album) + return album +} + +func fillAlbum(subAlbum *subsonic.AlbumID3, album *mediaprovider.Album) { + album.ID = subAlbum.ID + album.CoverArtID = subAlbum.CoverArt + album.Name = subAlbum.Name + album.Duration = subAlbum.Duration + album.ArtistIDs = []string{subAlbum.ArtistID} + album.ArtistNames = []string{subAlbum.Artist} + album.Year = subAlbum.Year + album.TrackCount = subAlbum.SongCount + album.Genres = []string{subAlbum.Genre} + album.Favorite = !subAlbum.Starred.IsZero() } func toArtist(ar *subsonic.Artist) *mediaprovider.Artist { @@ -360,14 +313,21 @@ func toArtistFromID3(ar *subsonic.ArtistID3) *mediaprovider.Artist { } func toPlaylist(pl *subsonic.Playlist) *mediaprovider.Playlist { - return &mediaprovider.Playlist{ - Name: pl.Name, - ID: pl.ID, - CoverArtID: pl.CoverArt, - Description: pl.Comment, - Owner: pl.Owner, - Public: pl.Public, - TrackCount: pl.SongCount, - Duration: pl.Duration, + if pl == nil { + return nil } + playlist := &mediaprovider.Playlist{} + fillPlaylist(pl, playlist) + return playlist +} + +func fillPlaylist(pl *subsonic.Playlist, playlist *mediaprovider.Playlist) { + playlist.Name = pl.Name + playlist.ID = pl.ID + playlist.CoverArtID = pl.CoverArt + playlist.Description = pl.Comment + playlist.Owner = pl.Owner + playlist.Public = pl.Public + playlist.TrackCount = pl.SongCount + playlist.Duration = pl.Duration } diff --git a/backend/mediaprovider/subsonic/trackiterator.go b/backend/mediaprovider/subsonic/trackiterator.go index 98f9481..3758368 100644 --- a/backend/mediaprovider/subsonic/trackiterator.go +++ b/backend/mediaprovider/subsonic/trackiterator.go @@ -11,7 +11,7 @@ func (s *subsonicMediaProvider) IterateTracks(searchQuery string) mediaprovider. if searchQuery == "" { return &allTracksIterator{ s: s, - albumIter: s.IterateAlbums(AlbumSortArtistAZ, "", mediaprovider.AlbumFilter{}), + albumIter: s.IterateAlbums(AlbumSortArtistAZ, mediaprovider.AlbumFilter{}), } } return &searchTracksIterator{ diff --git a/ui/browsing/albumspage.go b/ui/browsing/albumspage.go index 52e9b5b..c4cbfe4 100644 --- a/ui/browsing/albumspage.go +++ b/ui/browsing/albumspage.go @@ -74,7 +74,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, cfg.SortOrder = string(mp.AlbumSortOrders()[0]) } a.sortOrder.Selected = cfg.SortOrder - iter := mp.IterateAlbums(a.sortOrder.Selected, "", a.filter) + iter := mp.IterateAlbums(a.sortOrder.Selected, a.filter) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), im) contr.ConnectAlbumGridActions(a.grid) a.createSearchAndFilter() @@ -163,7 +163,7 @@ func (a *AlbumsPage) Reload() { if a.searchText != "" { a.doSearch(a.searchText) } else { - iter := a.mp.IterateAlbums(a.sortOrder.Selected, "", a.filter) + iter := a.mp.IterateAlbums(a.sortOrder.Selected, a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) a.grid.Refresh() } @@ -188,7 +188,7 @@ func (a *AlbumsPage) Save() SavedPage { } func (a *AlbumsPage) doSearch(query string) { - iter := widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums("", query, a.filter)) + iter := widgets.NewGridViewAlbumIterator(a.mp.SearchAlbums(query, a.filter)) if a.searchGrid == nil { a.searchGrid = widgets.NewGridView(iter, a.im) a.contr.ConnectAlbumGridActions(a.searchGrid) @@ -201,7 +201,7 @@ func (a *AlbumsPage) doSearch(query string) { func (a *AlbumsPage) onSortOrderChanged(order string) { a.cfg.SortOrder = a.sortOrder.Selected - iter := a.mp.IterateAlbums(order, "", a.filter) + iter := a.mp.IterateAlbums(order, a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) if a.searchText == "" { a.container.Objects[0] = a.grid diff --git a/ui/browsing/artistpage.go b/ui/browsing/artistpage.go index 86f0b41..e1a4c95 100644 --- a/ui/browsing/artistpage.go +++ b/ui/browsing/artistpage.go @@ -30,7 +30,7 @@ type artistPageState struct { cfg *backend.ArtistPageConfig pm *backend.PlaybackManager - sm *backend.ServerManager + mp mediaprovider.MediaProvider im *backend.ImageManager contr *controller.Controller } @@ -49,20 +49,20 @@ type ArtistPage struct { container *fyne.Container } -func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, sm *backend.ServerManager, im *backend.ImageManager, contr *controller.Controller) *ArtistPage { +func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage { activeView := 0 if cfg.InitialView == "Top Tracks" { activeView = 1 } - return newArtistPage(artistID, cfg, pm, sm, im, contr, activeView) + return newArtistPage(artistID, cfg, pm, mp, im, contr, activeView) } -func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, sm *backend.ServerManager, im *backend.ImageManager, contr *controller.Controller, activeView int) *ArtistPage { +func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller, activeView int) *ArtistPage { a := &ArtistPage{artistPageState: artistPageState{ artistID: artistID, cfg: cfg, pm: pm, - sm: sm, + mp: mp, im: im, contr: contr, activeView: activeView, @@ -141,7 +141,7 @@ func (a *ArtistPage) playArtistRadio() { // should be called asynchronously func (a *ArtistPage) load() { - artist, err := a.sm.Server.GetArtist(a.artistID) + artist, err := a.mp.GetArtist(a.artistID) if err != nil { log.Printf("Failed to get artist: %s", err.Error()) return @@ -153,7 +153,7 @@ func (a *ArtistPage) load() { } else { a.showTopTracks() } - info, err := a.sm.Server.GetArtistInfo(a.artistID) + info, err := a.mp.GetArtistInfo(a.artistID) if err != nil { log.Printf("Failed to get artist info: %s", err.Error()) } @@ -189,7 +189,7 @@ func (a *ArtistPage) showTopTracks() { a.activeView = 1 // if page still loading, will show tracks view first return } - ts, err := a.sm.Server.GetTopTracks(a.artistInfo.Artist, 20) + ts, err := a.mp.GetTopTracks(a.artistInfo.Artist, 20) if err != nil { log.Printf("error getting top songs: %s", err.Error()) return @@ -232,7 +232,7 @@ func (a *ArtistPage) CreateRenderer() fyne.WidgetRenderer { } func (s *artistPageState) Restore() Page { - return newArtistPage(s.artistID, s.cfg, s.pm, s.sm, s.im, s.contr, s.activeView) + return newArtistPage(s.artistID, s.cfg, s.pm, s.mp, s.im, s.contr, s.activeView) } type ArtistPageHeader struct { @@ -333,7 +333,7 @@ func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) { func (a *ArtistPageHeader) toggleFavorited() { params := mediaprovider.RatingFavoriteParameters{ArtistIDs: []string{a.artistID}} - a.artistPage.sm.Server.SetFavorite(params, a.favoriteBtn.IsFavorited) + a.artistPage.mp.SetFavorite(params, a.favoriteBtn.IsFavorited) } func (a *ArtistPageHeader) createContainer() { diff --git a/ui/browsing/artistsgenrespage.go b/ui/browsing/artistsgenrespage.go index 8a7078f..1b5901f 100644 --- a/ui/browsing/artistsgenrespage.go +++ b/ui/browsing/artistsgenrespage.go @@ -4,7 +4,6 @@ import ( "log" "strings" - "github.com/dweymouth/supersonic/backend" "github.com/dweymouth/supersonic/backend/mediaprovider" "github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/ui/controller" @@ -25,7 +24,7 @@ type ArtistsGenresPage struct { isGenresPage bool contr *controller.Controller - sm *backend.ServerManager + mp mediaprovider.MediaProvider model []widgets.ArtistGenreListItemModel list *widgets.ArtistGenreList @@ -34,11 +33,11 @@ type ArtistsGenresPage struct { searcher *widgets.SearchEntry } -func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *backend.ServerManager) *ArtistsGenresPage { - return newArtistsGenresPage(isGenresPage, contr, sm, "") +func NewArtistsGenresPage(isGenresPage bool, contr *controller.Controller, mp mediaprovider.MediaProvider) *ArtistsGenresPage { + return newArtistsGenresPage(isGenresPage, contr, mp, "") } -func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *backend.ServerManager, searchText string) *ArtistsGenresPage { +func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, mp mediaprovider.MediaProvider, searchText string) *ArtistsGenresPage { title := "Artists" if isGenresPage { title = "Genres" @@ -46,7 +45,7 @@ func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *b a := &ArtistsGenresPage{ isGenresPage: isGenresPage, contr: contr, - sm: sm, + mp: mp, titleDisp: widget.NewRichTextWithText(title), } a.ExtendBaseWidget(a) @@ -72,13 +71,13 @@ func newArtistsGenresPage(isGenresPage bool, contr *controller.Controller, sm *b // should be called asynchronously func (a *ArtistsGenresPage) load(searchOnLoad bool) { if a.isGenresPage { - genres, err := a.sm.Server.GetGenres() + genres, err := a.mp.GetGenres() if err != nil { log.Printf("error loading genres: %v", err.Error()) } a.model = a.buildGenresListModel(genres) } else { - artists, err := a.sm.Server.GetArtists() + artists, err := a.mp.GetArtists() if err != nil { log.Printf("error loading artists: %v", err.Error()) } @@ -127,7 +126,7 @@ func (a *ArtistsGenresPage) Save() SavedPage { return &savedArtistsGenresPage{ isGenresPage: a.isGenresPage, contr: a.contr, - sm: a.sm, + mp: a.mp, searchText: a.searcher.Entry.Text, } } @@ -135,12 +134,12 @@ func (a *ArtistsGenresPage) Save() SavedPage { type savedArtistsGenresPage struct { isGenresPage bool contr *controller.Controller - sm *backend.ServerManager + mp mediaprovider.MediaProvider searchText string } func (s *savedArtistsGenresPage) Restore() Page { - return newArtistsGenresPage(s.isGenresPage, s.contr, s.sm, s.searchText) + return newArtistsGenresPage(s.isGenresPage, s.contr, s.mp, s.searchText) } func (a *ArtistsGenresPage) buildArtistListModel(artists []*mediaprovider.Artist) []widgets.ArtistGenreListItemModel { diff --git a/ui/browsing/favoritespage.go b/ui/browsing/favoritespage.go index 9a8ed65..aec026a 100644 --- a/ui/browsing/favoritespage.go +++ b/ui/browsing/favoritespage.go @@ -25,7 +25,7 @@ type FavoritesPage struct { contr *controller.Controller pm *backend.PlaybackManager im *backend.ImageManager - sm *backend.ServerManager + mp mediaprovider.MediaProvider filter mediaprovider.AlbumFilter searchText string @@ -43,18 +43,18 @@ type FavoritesPage struct { container *fyne.Container } -func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage { +func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, mp mediaprovider.MediaProvider, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage { a := &FavoritesPage{ filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true}, cfg: cfg, contr: contr, pm: pm, - sm: sm, + mp: mp, im: im, } a.ExtendBaseWidget(a) a.createHeader(0) - iter := sm.Server.IterateAlbums("", "", a.filter) + iter := mp.IterateAlbums("", a.filter) a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) a.contr.ConnectAlbumGridActions(a.grid) if cfg.InitialView == "Artists" { @@ -98,7 +98,7 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage { cfg: saved.cfg, contr: saved.contr, pm: saved.pm, - sm: saved.sm, + mp: saved.mp, im: saved.im, searchText: saved.searchText, filter: saved.filter, @@ -139,13 +139,13 @@ func (a *FavoritesPage) Reload() { if a.searchText != "" { a.doSearchAlbums(a.searchText) } else { - iter := a.sm.Server.IterateAlbums("", "", a.filter) + iter := a.mp.IterateAlbums("", a.filter) a.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) } if a.tracklistCtr != nil || a.artistListCtr != nil { go func() { // re-fetch starred info from server - starred, err := a.sm.Server.GetFavorites() + starred, err := a.mp.GetFavorites() if err != nil { log.Printf("error getting starred items: %s", err.Error()) return @@ -177,7 +177,7 @@ func (a *FavoritesPage) Save() SavedPage { cfg: a.cfg, contr: a.contr, pm: a.pm, - sm: a.sm, + mp: a.mp, im: a.im, filter: a.filter, searchText: a.searchText, @@ -230,7 +230,7 @@ func (a *FavoritesPage) SelectAll() { } func (a *FavoritesPage) doSearchAlbums(query string) { - iter := a.sm.Server.IterateAlbums("", query, a.filter) + iter := a.mp.SearchAlbums(query, a.filter) if a.searchGrid == nil { a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im) a.contr.ConnectAlbumGridActions(a.searchGrid) @@ -266,7 +266,7 @@ func (a *FavoritesPage) onShowFavoriteArtists() { a.createContainer(layout.NewSpacer()) } go func() { - fav, err := a.sm.Server.GetFavorites() + fav, err := a.mp.GetFavorites() if err != nil { log.Printf("error getting starred items: %s", err.Error()) return @@ -315,7 +315,7 @@ func (a *FavoritesPage) onShowFavoriteSongs() { a.createContainer(layout.NewSpacer()) } go func() { - fav, err := a.sm.Server.GetFavorites() + fav, err := a.mp.GetFavorites() if err != nil { log.Printf("error getting starred items: %s", err.Error()) return @@ -350,7 +350,7 @@ type savedFavoritesPage struct { cfg *backend.FavoritesPageConfig contr *controller.Controller pm *backend.PlaybackManager - sm *backend.ServerManager + mp mediaprovider.MediaProvider im *backend.ImageManager gridState widgets.GridViewState searchGridState widgets.GridViewState diff --git a/ui/browsing/genrepage.go b/ui/browsing/genrepage.go index e8c2f10..be3b527 100644 --- a/ui/browsing/genrepage.go +++ b/ui/browsing/genrepage.go @@ -54,7 +54,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.mp.IterateAlbums("", "", g.filter) + iter := g.mp.IterateAlbums("", g.filter) g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.contr.ConnectAlbumGridActions(g.grid) g.createSearchAndFilter() @@ -125,7 +125,7 @@ func (g *GenrePage) Reload() { if g.searchText != "" { g.doSearch(g.searchText) } else { - iter := g.mp.IterateAlbums("", "", g.filter) + iter := g.mp.IterateAlbums("", g.filter) g.grid.Reset(widgets.NewGridViewAlbumIterator(iter)) g.grid.Refresh() } @@ -168,7 +168,7 @@ func (g *GenrePage) OnSearched(query string) { } func (g *GenrePage) doSearch(query string) { - iter := g.mp.IterateAlbums("", query, g.filter) + iter := g.mp.SearchAlbums(query, g.filter) if g.searchGrid == nil { g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im) g.contr.ConnectAlbumGridActions(g.searchGrid) diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 03ea70b..ff1f0b4 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -27,7 +27,6 @@ type NowPlayingPage struct { type nowPlayingPageState struct { contr *controller.Controller conf *backend.NowPlayingPageConfig - sm *backend.ServerManager pm *backend.PlaybackManager } @@ -35,10 +34,9 @@ func NewNowPlayingPage( highlightedTrackID string, contr *controller.Controller, conf *backend.NowPlayingPageConfig, - sm *backend.ServerManager, pm *backend.PlaybackManager, ) *NowPlayingPage { - a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, sm: sm, pm: pm}} + a := &NowPlayingPage{nowPlayingPageState: nowPlayingPageState{contr: contr, conf: conf, pm: pm}} a.ExtendBaseWidget(a) a.tracklist = widgets.NewTracklist(nil) a.tracklist.SetVisibleColumns(conf.TracklistColumns) @@ -117,5 +115,5 @@ func (a *NowPlayingPage) load(highlightedTrackID string) { } func (s *nowPlayingPageState) Restore() Page { - return NewNowPlayingPage("", s.contr, s.conf, s.sm, s.pm) + return NewNowPlayingPage("", s.contr, s.conf, s.pm) } diff --git a/ui/browsing/playlistspage.go b/ui/browsing/playlistspage.go index 2ebbf52..ba9eb07 100644 --- a/ui/browsing/playlistspage.go +++ b/ui/browsing/playlistspage.go @@ -26,7 +26,7 @@ type PlaylistsPage struct { cfg *backend.PlaylistsPageConfig contr *controller.Controller - sm *backend.ServerManager + mp mediaprovider.MediaProvider playlists []*mediaprovider.Playlist searchedPlaylists []*mediaprovider.Playlist @@ -38,18 +38,18 @@ type PlaylistsPage struct { gridView *widgets.GridView } -func NewPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, sm *backend.ServerManager) *PlaylistsPage { +func NewPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider) *PlaylistsPage { activeView := 0 if cfg.InitialView == "Grid" { activeView = 1 } - return newPlaylistsPage(contr, cfg, sm, "", activeView) + return newPlaylistsPage(contr, cfg, mp, "", activeView) } -func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, sm *backend.ServerManager, searchText string, activeView int) *PlaylistsPage { +func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageConfig, mp mediaprovider.MediaProvider, searchText string, activeView int) *PlaylistsPage { a := &PlaylistsPage{ cfg: cfg, - sm: sm, + mp: mp, contr: contr, titleDisp: widget.NewRichTextWithText("Playlists"), } @@ -75,7 +75,7 @@ func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageCo } func (a *PlaylistsPage) load(searchOnLoad bool) { - playlists, err := a.sm.Server.GetPlaylists() + playlists, err := a.mp.GetPlaylists() if err != nil { log.Printf("error loading playlists: %v", err.Error()) } @@ -213,7 +213,7 @@ func (a *PlaylistsPage) Save() SavedPage { return &savedPlaylistsPage{ contr: a.contr, cfg: a.cfg, - sm: a.sm, + mp: a.mp, searchText: a.searcher.Entry.Text, activeView: a.viewToggle.ActivatedButtonIndex(), } @@ -222,13 +222,13 @@ func (a *PlaylistsPage) Save() SavedPage { type savedPlaylistsPage struct { contr *controller.Controller cfg *backend.PlaylistsPageConfig - sm *backend.ServerManager + mp mediaprovider.MediaProvider searchText string activeView int } func (s *savedPlaylistsPage) Restore() Page { - return newPlaylistsPage(s.contr, s.cfg, s.sm, s.searchText, s.activeView) + return newPlaylistsPage(s.contr, s.cfg, s.mp, s.searchText, s.activeView) } func (a *PlaylistsPage) buildContainer(initialView fyne.CanvasObject) { diff --git a/ui/browsing/router.go b/ui/browsing/router.go index 10474d3..c337347 100644 --- a/ui/browsing/router.go +++ b/ui/browsing/router.go @@ -32,21 +32,21 @@ func (r Router) CreatePage(rte controller.Route) Page { case controller.Albums: return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) case controller.Artist: - return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, r.App.PlaybackManager, r.App.ServerManager, r.App.ImageManager, r.Controller) + return NewArtistPage(rte.Arg, &r.App.Config.ArtistPage, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller) case controller.Artists: - return NewArtistsGenresPage(false, r.Controller, r.App.ServerManager) + return NewArtistsGenresPage(false, r.Controller, r.App.ServerManager.Server) case controller.Favorites: - return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager) + return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager.Server, r.App.PlaybackManager, r.App.ImageManager) case controller.Genre: return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager) case controller.Genres: - return NewArtistsGenresPage(true, r.Controller, r.App.ServerManager) + return NewArtistsGenresPage(true, r.Controller, r.App.ServerManager.Server) case controller.NowPlaying: - return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.ServerManager, r.App.PlaybackManager) + return NewNowPlayingPage(rte.Arg, r.Controller, &r.App.Config.NowPlayingPage, r.App.PlaybackManager) case controller.Playlist: return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager) case controller.Playlists: - return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager) + return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager.Server) case controller.Tracks: return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.ServerManager.Server) } diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 98eaef3..52c7665 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -128,12 +128,14 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { m.NavigateTo(ArtistRoute(artistID)) } grid.OnAddToPlaylist = func(albumID string) { - album, err := m.App.ServerManager.Server.GetAlbum(albumID) - if err != nil { - log.Printf("error loading album: %s", err.Error()) - return - } - m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Tracks)) + go func() { + album, err := m.App.ServerManager.Server.GetAlbum(albumID) + if err != nil { + log.Printf("error loading album: %s", err.Error()) + return + } + m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Tracks)) + }() } } @@ -169,36 +171,38 @@ func (m *Controller) PromptForFirstServer() { // Depending on the results of that dialog, potentially create a new playlist // Add tracks to the user-specified playlist func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) { - pls, err := m.App.ServerManager.Server.GetPlaylists() - pls = sharedutil.FilterSlice(pls, func(pl *mediaprovider.Playlist) bool { - return pl.Owner == m.App.ServerManager.LoggedInUser - }) - if err != nil { - // TODO: surface this error to user - log.Printf("error getting user-owned playlists: %s", err.Error()) - return - } - plNames := make([]string, 0, len(pls)) - for _, pl := range pls { - plNames = append(plNames, pl.Name) - } - - dlg := dialogs.NewAddToPlaylistDialog("Add to Playlist", plNames) - pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas()) - m.ClosePopUpOnEscape(pop) - dlg.OnCanceled = pop.Hide - dlg.OnSubmit = func(playlistChoice int, newPlaylistName string) { - pop.Hide() - m.doModalClosed() - if playlistChoice < 0 { - m.App.ServerManager.Server.CreatePlaylist(newPlaylistName, trackIDs) - } else { - m.App.ServerManager.Server.EditPlaylistTracks( - pls[playlistChoice].ID, trackIDs, nil /*tracksToRemove*/) + go func() { + pls, err := m.App.ServerManager.Server.GetPlaylists() + pls = sharedutil.FilterSlice(pls, func(pl *mediaprovider.Playlist) bool { + return pl.Owner == m.App.ServerManager.LoggedInUser + }) + if err != nil { + // TODO: surface this error to user + log.Printf("error getting user-owned playlists: %s", err.Error()) + return } - } - m.haveModal = true - pop.Show() + plNames := make([]string, 0, len(pls)) + for _, pl := range pls { + plNames = append(plNames, pl.Name) + } + + dlg := dialogs.NewAddToPlaylistDialog("Add to Playlist", plNames) + pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas()) + m.ClosePopUpOnEscape(pop) + dlg.OnCanceled = pop.Hide + dlg.OnSubmit = func(playlistChoice int, newPlaylistName string) { + pop.Hide() + m.doModalClosed() + if playlistChoice < 0 { + go m.App.ServerManager.Server.CreatePlaylist(newPlaylistName, trackIDs) + } else { + go m.App.ServerManager.Server.EditPlaylistTracks( + pls[playlistChoice].ID, trackIDs, nil /*tracksToRemove*/) + } + } + m.haveModal = true + pop.Show() + }() } func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) { @@ -399,7 +403,7 @@ func (c *Controller) doModalClosed() { } func (c *Controller) SetTrackFavorites(trackIDs []string, favorite bool) { - c.App.ServerManager.Server.SetFavorite(mediaprovider.RatingFavoriteParameters{ + go c.App.ServerManager.Server.SetFavorite(mediaprovider.RatingFavoriteParameters{ TrackIDs: trackIDs, }, favorite) @@ -409,7 +413,7 @@ func (c *Controller) SetTrackFavorites(trackIDs []string, favorite bool) { } func (c *Controller) SetTrackRatings(trackIDs []string, rating int) { - c.App.ServerManager.Server.SetRating(mediaprovider.RatingFavoriteParameters{ + go c.App.ServerManager.Server.SetRating(mediaprovider.RatingFavoriteParameters{ TrackIDs: trackIDs, }, rating) diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index 3b455a7..7c583cf 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -608,11 +608,11 @@ func (t *TrackRow) Update(tr *mediaprovider.Track, rowNum int) { // Render favorite column if tr.Favorite { - t.isFavorite = false - t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon - } else { t.isFavorite = true t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.FavoriteIcon + } else { + t.isFavorite = false + t.favorite.Objects[0].(*TappableIcon).Resource = myTheme.NotFavoriteIcon } t.rating.Rating = tr.Rating