more refactoring - compiles now but not extensively tested

This commit is contained in:
Drew Weymouth
2023-05-14 20:50:06 -07:00
parent 85042fcbd4
commit fd106560d0
20 changed files with 216 additions and 249 deletions
+3 -5
View File
@@ -25,7 +25,6 @@ type App struct {
Config *Config
ServerManager *ServerManager
ImageManager *ImageManager
LibraryManager *LibraryManager
PlaybackManager *PlaybackManager
Player *player.Player
UpdateChecker UpdateChecker
@@ -63,11 +62,10 @@ func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*A
a.ServerManager = NewServerManager(appName)
a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player, &a.Config.Scrobbling)
a.LibraryManager = NewLibraryManager(a.ServerManager)
a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(a.appName))
a.LibraryManager.PreCacheCoverFn = func(coverID string) {
_, _ = a.ImageManager.GetCoverThumbnail(coverID)
}
//a.LibraryManager.PreCacheCoverFn = func(coverID string) {
//_, _ = a.ImageManager.GetCoverThumbnail(coverID)
//}
return a, nil
}
+1 -1
View File
@@ -124,7 +124,7 @@ func DefaultConfig(appVersionTag string) *Config {
TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite", "Rating"},
},
AlbumsPage: AlbumsPageConfig{
SortOrder: string(AlbumSortRecentlyAdded),
SortOrder: string("Recently Added"),
},
ArtistPage: ArtistPageConfig{
InitialView: "Discography",
-25
View File
@@ -1,25 +0,0 @@
package backend
import (
subsonic "github.com/dweymouth/go-subsonic/subsonic"
)
type AlbumIterator interface {
Next() *subsonic.AlbumID3
}
type TrackIterator interface {
Next() *subsonic.Child
}
type LibraryManager struct {
PreCacheCoverFn func(coverID string)
s *ServerManager
}
func NewLibraryManager(s *ServerManager) *LibraryManager {
return &LibraryManager{
s: s,
}
}
+3 -1
View File
@@ -60,11 +60,13 @@ type MediaProvider interface {
GetStreamURL(trackID string) (string, error)
GetTopTracks(artist Artist, count int) ([]*Track, error)
SetFavorite(params RatingFavoriteParameters, favorite bool) error
SetRating(params RatingFavoriteParameters, rating int) error
GetPlaylists() ([]Playlist, error)
GetPlaylists() ([]*Playlist, error)
CreatePlaylist(name string, trackIDs []string) error
+2
View File
@@ -21,6 +21,7 @@ type AlbumWithTracks struct {
type Artist struct {
ID string
Name string
Favorite bool
AlbumCount int
}
@@ -71,6 +72,7 @@ type Playlist struct {
Description string
Public bool
Owner string
Duration int
TrackCount int
}
@@ -95,6 +95,7 @@ func (s *subsonicMediaProvider) GetArtist(artistID string) (*mediaprovider.Artis
Artist: mediaprovider.Artist{
ID: ar.ID,
Name: ar.Name,
Favorite: !ar.Starred.IsZero(),
AlbumCount: ar.AlbumCount,
},
Albums: sharedutil.MapSlice(ar.Album, toAlbum),
@@ -168,12 +169,21 @@ func (s *subsonicMediaProvider) GetPlaylist(playlistID string) (*mediaprovider.P
return nil, err
}
return &mediaprovider.PlaylistWithTracks{
Playlist: toPlaylist(pl),
Tracks: sharedutil.MapSlice(pl.Entry, toTrack),
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,
},
Tracks: sharedutil.MapSlice(pl.Entry, toTrack),
}, nil
}
func (s *subsonicMediaProvider) GetPlaylists() ([]mediaprovider.Playlist, error) {
func (s *subsonicMediaProvider) GetPlaylists() ([]*mediaprovider.Playlist, error) {
pl, err := s.client.GetPlaylists(map[string]string{})
if err != nil {
return nil, err
@@ -209,6 +219,18 @@ func (s *subsonicMediaProvider) GetStreamURL(trackID string) (string, error) {
return u.String(), nil
}
func (s *subsonicMediaProvider) GetTopTracks(artist mediaprovider.Artist, count int) ([]*mediaprovider.Track, error) {
params := map[string]string{}
if count > 0 {
params["count"] = strconv.Itoa(count)
}
tr, err := s.client.GetTopSongs(artist.Name, params)
if err != nil {
return nil, err
}
return sharedutil.MapSlice(tr, toTrack), nil
}
func (s *subsonicMediaProvider) ReplacePlaylistTracks(playlistID string, trackIDs []string) error {
return s.client.CreatePlaylistWithTracks(trackIDs, map[string]string{"playlistId": playlistID})
}
@@ -319,8 +341,9 @@ func toArtist(ar *subsonic.Artist) *mediaprovider.Artist {
return nil
}
return &mediaprovider.Artist{
ID: ar.ID,
Name: ar.Name,
ID: ar.ID,
Name: ar.Name,
Favorite: !ar.Starred.IsZero(),
}
}
@@ -331,16 +354,20 @@ func toArtistFromID3(ar *subsonic.ArtistID3) *mediaprovider.Artist {
return &mediaprovider.Artist{
ID: ar.ID,
Name: ar.Name,
Favorite: !ar.Starred.IsZero(),
AlbumCount: ar.AlbumCount,
}
}
func toPlaylist(pl *subsonic.Playlist) mediaprovider.Playlist {
return mediaprovider.Playlist{
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,
}
}
+6 -9
View File
@@ -34,19 +34,17 @@ type AlbumPage struct {
type albumPageState struct {
albumID string
cfg *backend.AlbumPageConfig
lm *backend.LibraryManager
mp mediaprovider.MediaProvider
pm *backend.PlaybackManager
im *backend.ImageManager
sm *backend.ServerManager
contr *controller.Controller
}
func NewAlbumPage(
albumID string,
cfg *backend.AlbumPageConfig,
sm *backend.ServerManager,
pm *backend.PlaybackManager,
lm *backend.LibraryManager,
mp mediaprovider.MediaProvider,
im *backend.ImageManager,
contr *controller.Controller,
) *AlbumPage {
@@ -54,9 +52,8 @@ func NewAlbumPage(
albumPageState: albumPageState{
albumID: albumID,
cfg: cfg,
sm: sm,
pm: pm,
lm: lm,
mp: mp,
im: im,
contr: contr,
},
@@ -115,7 +112,7 @@ func (a *AlbumPage) SelectAll() {
// should be called asynchronously
func (a *AlbumPage) load() {
album, err := a.sm.Server.GetAlbum(a.albumID)
album, err := a.mp.GetAlbum(a.albumID)
if err != nil {
log.Printf("Failed to get album: %s", err.Error())
return
@@ -238,7 +235,7 @@ func (a *AlbumPageHeader) Update(album *mediaprovider.AlbumWithTracks, im *backe
func (a *AlbumPageHeader) toggleFavorited() {
params := mediaprovider.RatingFavoriteParameters{AlbumIDs: []string{a.albumID}}
a.page.sm.Server.SetFavorite(params, a.toggleFavButton.IsFavorited)
a.page.mp.SetFavorite(params, a.toggleFavButton.IsFavorited)
}
func (a *AlbumPageHeader) showPopUpCover() {
@@ -263,5 +260,5 @@ func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
}
func (s *albumPageState) Restore() Page {
return NewAlbumPage(s.albumID, s.cfg, s.sm, s.pm, s.lm, s.im, s.contr)
return NewAlbumPage(s.albumID, s.cfg, s.pm, s.mp, s.im, s.contr)
}
+17 -16
View File
@@ -24,7 +24,7 @@ type AlbumsPage struct {
contr *controller.Controller
pm *backend.PlaybackManager
im *backend.ImageManager
lm *backend.LibraryManager
mp mediaprovider.MediaProvider
grid *widgets.GridView
searchGrid *widgets.GridView
searcher *widgets.SearchEntry
@@ -55,12 +55,12 @@ func (s *selectWidget) MinSize() fyne.Size {
return fyne.NewSize(170, s.Select.MinSize().Height)
}
func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager) *AlbumsPage {
func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *AlbumsPage {
a := &AlbumsPage{
cfg: cfg,
contr: contr,
pm: pm,
lm: lm,
mp: mp,
im: im,
}
a.ExtendBaseWidget(a)
@@ -69,12 +69,12 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, contr *controller.Controller,
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
SizeName: theme.SizeNameHeadingText,
}
a.sortOrder = NewSelect(backend.AlbumSortOrders, a.onSortOrderChanged)
if !sharedutil.SliceContains(backend.AlbumSortOrders, cfg.SortOrder) {
cfg.SortOrder = string(backend.AlbumSortRecentlyAdded)
a.sortOrder = NewSelect(mp.AlbumSortOrders(), a.onSortOrderChanged)
if !sharedutil.SliceContains(mp.AlbumSortOrders(), cfg.SortOrder) {
cfg.SortOrder = string(mp.AlbumSortOrders()[0])
}
a.sortOrder.Selected = cfg.SortOrder
iter := lm.AlbumsIter(backend.AlbumSortOrder(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()
@@ -112,7 +112,7 @@ func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage {
cfg: saved.cfg,
contr: saved.contr,
pm: saved.pm,
lm: saved.lm,
mp: saved.mp,
im: saved.im,
searchText: saved.searchText,
filter: saved.filter,
@@ -123,7 +123,7 @@ func restoreAlbumsPage(saved *savedAlbumsPage) *AlbumsPage {
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
SizeName: theme.SizeNameHeadingText,
}
a.sortOrder = NewSelect(backend.AlbumSortOrders, nil)
a.sortOrder = NewSelect(a.mp.AlbumSortOrders(), nil)
a.sortOrder.Selected = saved.sortOrder
a.sortOrder.OnChanged = a.onSortOrderChanged
a.grid = widgets.NewGridViewFromState(saved.gridState)
@@ -163,7 +163,7 @@ func (a *AlbumsPage) Reload() {
if a.searchText != "" {
a.doSearch(a.searchText)
} else {
iter := a.lm.AlbumsIter(backend.AlbumSortOrder(a.sortOrder.Selected), a.filter)
iter := a.mp.IterateAlbums(a.sortOrder.Selected, "", a.filter)
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
a.grid.Refresh()
}
@@ -174,7 +174,7 @@ func (a *AlbumsPage) Save() SavedPage {
cfg: a.cfg,
contr: a.contr,
pm: a.pm,
lm: a.lm,
mp: a.mp,
im: a.im,
searchText: a.searchText,
filter: a.filter,
@@ -188,11 +188,12 @@ func (a *AlbumsPage) Save() SavedPage {
}
func (a *AlbumsPage) doSearch(query string) {
iter := widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums("", query, a.filter))
if a.searchGrid == nil {
a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(a.lm.SearchIter(query)), a.im)
a.searchGrid = widgets.NewGridView(iter, a.im)
a.contr.ConnectAlbumGridActions(a.searchGrid)
} else {
a.searchGrid.Reset(widgets.NewGridViewAlbumIterator(a.lm.SearchIterWithFilter(query, a.filter)))
a.searchGrid.Reset(iter)
}
a.container.Objects[0] = a.searchGrid
a.Refresh()
@@ -200,7 +201,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), a.filter)
iter := a.mp.IterateAlbums(order, "", a.filter)
a.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
if a.searchText == "" {
a.container.Objects[0] = a.grid
@@ -215,11 +216,11 @@ func (a *AlbumsPage) CreateRenderer() fyne.WidgetRenderer {
type savedAlbumsPage struct {
searchText string
filter backend.AlbumFilter
filter mediaprovider.AlbumFilter
cfg *backend.AlbumsPageConfig
contr *controller.Controller
pm *backend.PlaybackManager
lm *backend.LibraryManager
mp mediaprovider.MediaProvider
im *backend.ImageManager
sortOrder string
gridState widgets.GridViewState
+15 -20
View File
@@ -20,8 +20,6 @@ import (
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
var _ fyne.Widget = (*ArtistPage)(nil)
@@ -42,7 +40,7 @@ type ArtistPage struct {
artistPageState
artistInfo *subsonic.ArtistID3
artistInfo *mediaprovider.ArtistWithAlbums
albumGrid *widgets.GridView
tracklistCtr *fyne.Container
@@ -130,7 +128,7 @@ func (a *ArtistPage) OnSongChange(track, lastScrobbledIfAny *mediaprovider.Track
func (a *ArtistPage) playAllTracks() {
if a.artistInfo != nil { // page loaded
for i, album := range a.artistInfo.Album {
for i, album := range a.artistInfo.Albums {
a.pm.LoadAlbum(album.ID, i > 0 /*append*/, false /*shuffle*/)
}
a.pm.PlayFromBeginning()
@@ -155,7 +153,7 @@ func (a *ArtistPage) load() {
} else {
a.showTopTracks()
}
info, err := a.sm.Server.GetArtistInfo2(a.artistID, nil)
info, err := a.sm.Server.GetArtistInfo(a.artistID)
if err != nil {
log.Printf("Failed to get artist info: %s", err.Error())
}
@@ -169,11 +167,11 @@ func (a *ArtistPage) showAlbumGrid() {
a.activeView = 0 // if page still loading, will show discography view first
return
}
model := sharedutil.MapSlice(a.artistInfo.Album, func(al *subsonic.AlbumID3) widgets.GridViewItemModel {
model := sharedutil.MapSlice(a.artistInfo.Albums, func(al *mediaprovider.Album) widgets.GridViewItemModel {
return widgets.GridViewItemModel{
Name: al.Name,
ID: al.ID,
CoverArtID: al.CoverArt,
CoverArtID: al.CoverArtID,
Secondary: strconv.Itoa(al.Year),
}
})
@@ -191,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.GetTopSongs(a.artistInfo.Name, map[string]string{"count": "20"})
ts, err := a.sm.Server.GetTopTracks(a.artistInfo.Artist, 20)
if err != nil {
log.Printf("error getting top songs: %s", err.Error())
return
@@ -277,18 +275,18 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
return a
}
func (a *ArtistPageHeader) Update(artist *subsonic.ArtistID3) {
func (a *ArtistPageHeader) Update(artist *mediaprovider.ArtistWithAlbums) {
if artist == nil {
return
}
a.favoriteBtn.IsFavorited = !artist.Starred.IsZero()
a.favoriteBtn.IsFavorited = !artist.Favorite
a.favoriteBtn.Refresh()
a.artistID = artist.ID
a.titleDisp.Segments[0].(*widget.TextSegment).Text = artist.Name
a.titleDisp.Refresh()
}
func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) {
func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) {
if info == nil {
return
}
@@ -304,7 +302,7 @@ func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) {
}
a.similarArtists.RemoveAll()
for i, art := range info.SimilarArtist {
for i, art := range info.SimilarArtists {
if i == 0 {
a.similarArtists.Add(widget.NewLabel("Similar Artists:"))
}
@@ -321,11 +319,11 @@ func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) {
}
a.similarArtists.Refresh()
if info.LargeImageUrl != "" {
if info.ImageURL != "" {
if a.artistImage.HaveImage() {
_ = a.artistPage.im.RefreshCachedArtistImageIfExpired(a.artistID, info.LargeImageUrl)
_ = a.artistPage.im.RefreshCachedArtistImageIfExpired(a.artistID, info.ImageURL)
} else {
im, err := a.artistPage.im.FetchAndCacheArtistImage(a.artistID, info.LargeImageUrl)
im, err := a.artistPage.im.FetchAndCacheArtistImage(a.artistID, info.ImageURL)
if err == nil {
a.artistImage.SetImage(im, true /*tappable*/)
}
@@ -334,11 +332,8 @@ func (a *ArtistPageHeader) UpdateInfo(info *subsonic.ArtistInfo2) {
}
func (a *ArtistPageHeader) toggleFavorited() {
if a.favoriteBtn.IsFavorited {
a.artistPage.sm.Server.Star(subsonic.StarParameters{ArtistIDs: []string{a.artistID}})
} else {
a.artistPage.sm.Server.Unstar(subsonic.StarParameters{ArtistIDs: []string{a.artistID}})
}
params := mediaprovider.RatingFavoriteParameters{ArtistIDs: []string{a.artistID}}
a.artistPage.sm.Server.SetFavorite(params, a.favoriteBtn.IsFavorited)
}
func (a *ArtistPageHeader) createContainer() {
+12 -16
View File
@@ -3,9 +3,9 @@ package browsing
import (
"log"
"strings"
"time"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/layouts"
@@ -16,8 +16,6 @@ import (
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
var _ fyne.Widget = (*ArtistPage)(nil)
@@ -80,7 +78,7 @@ func (a *ArtistsGenresPage) load(searchOnLoad bool) {
}
a.model = a.buildGenresListModel(genres)
} else {
artists, err := a.sm.Server.GetArtists(nil)
artists, err := a.sm.Server.GetArtists()
if err != nil {
log.Printf("error loading artists: %v", err.Error())
}
@@ -145,29 +143,27 @@ func (s *savedArtistsGenresPage) Restore() Page {
return newArtistsGenresPage(s.isGenresPage, s.contr, s.sm, s.searchText)
}
func (a *ArtistsGenresPage) buildArtistListModel(artists *subsonic.ArtistsID3) []widgets.ArtistGenreListItemModel {
func (a *ArtistsGenresPage) buildArtistListModel(artists []*mediaprovider.Artist) []widgets.ArtistGenreListItemModel {
model := make([]widgets.ArtistGenreListItemModel, 0)
for _, idx := range artists.Index {
for _, artist := range idx.Artist {
model = append(model, widgets.ArtistGenreListItemModel{
ID: artist.ID,
Name: artist.Name,
AlbumCount: artist.AlbumCount,
Favorite: artist.Starred != time.Time{},
})
}
for _, artist := range artists {
model = append(model, widgets.ArtistGenreListItemModel{
ID: artist.ID,
Name: artist.Name,
AlbumCount: artist.AlbumCount,
Favorite: artist.Favorite,
})
}
return model
}
func (a *ArtistsGenresPage) buildGenresListModel(genres []*subsonic.Genre) []widgets.ArtistGenreListItemModel {
func (a *ArtistsGenresPage) buildGenresListModel(genres []*mediaprovider.Genre) []widgets.ArtistGenreListItemModel {
model := make([]widgets.ArtistGenreListItemModel, 0)
for _, genre := range genres {
model = append(model, widgets.ArtistGenreListItemModel{
ID: genre.Name,
Name: genre.Name,
AlbumCount: genre.AlbumCount,
TrackCount: genre.SongCount,
TrackCount: genre.TrackCount,
Favorite: false,
})
}
+9 -14
View File
@@ -26,9 +26,8 @@ type FavoritesPage struct {
pm *backend.PlaybackManager
im *backend.ImageManager
sm *backend.ServerManager
lm *backend.LibraryManager
filter backend.AlbumFilter
filter mediaprovider.AlbumFilter
searchText string
nowPlayingID string
pendingViewSwitch bool
@@ -44,19 +43,18 @@ type FavoritesPage struct {
container *fyne.Container
}
func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager) *FavoritesPage {
func NewFavoritesPage(cfg *backend.FavoritesPageConfig, contr *controller.Controller, sm *backend.ServerManager, pm *backend.PlaybackManager, im *backend.ImageManager) *FavoritesPage {
a := &FavoritesPage{
filter: backend.AlbumFilter{ExcludeUnfavorited: true},
filter: mediaprovider.AlbumFilter{ExcludeUnfavorited: true},
cfg: cfg,
contr: contr,
pm: pm,
lm: lm,
sm: sm,
im: im,
}
a.ExtendBaseWidget(a)
a.createHeader(0)
iter := lm.StarredIter(a.filter)
iter := sm.Server.IterateAlbums("", "", a.filter)
a.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im)
a.contr.ConnectAlbumGridActions(a.grid)
if cfg.InitialView == "Artists" {
@@ -100,7 +98,6 @@ func restoreFavoritesPage(saved *savedFavoritesPage) *FavoritesPage {
cfg: saved.cfg,
contr: saved.contr,
pm: saved.pm,
lm: saved.lm,
sm: saved.sm,
im: saved.im,
searchText: saved.searchText,
@@ -142,13 +139,13 @@ func (a *FavoritesPage) Reload() {
if a.searchText != "" {
a.doSearchAlbums(a.searchText)
} else {
iter := a.lm.StarredIter(a.filter)
iter := a.sm.Server.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.GetStarred2(nil)
starred, err := a.sm.Server.GetFavorites()
if err != nil {
log.Printf("error getting starred items: %s", err.Error())
return
@@ -156,7 +153,7 @@ func (a *FavoritesPage) Reload() {
if a.tracklistCtr != nil {
// refresh favorite songs view
tr := a.tracklistCtr.Objects[0].(*widgets.Tracklist)
tr.Tracks = starred.Song
tr.Tracks = starred.Tracks
if a.toggleBtns.ActivatedButtonIndex() == 2 {
// favorite songs view is visible
tr.Refresh()
@@ -165,7 +162,7 @@ func (a *FavoritesPage) Reload() {
if a.artistListCtr != nil {
// refresh favorite artists view
al := a.artistListCtr.Objects[0].(*widgets.ArtistGenreList)
al.Items = buildArtistListModel(starred.Artist)
al.Items = buildArtistListModel(starred.Artists)
if a.toggleBtns.ActivatedButtonIndex() == 1 {
// favorite artists view is visible
al.Refresh()
@@ -182,7 +179,6 @@ func (a *FavoritesPage) Save() SavedPage {
pm: a.pm,
sm: a.sm,
im: a.im,
lm: a.lm,
filter: a.filter,
searchText: a.searchText,
gridState: a.grid.SaveToState(),
@@ -234,7 +230,7 @@ func (a *FavoritesPage) SelectAll() {
}
func (a *FavoritesPage) doSearchAlbums(query string) {
iter := a.lm.SearchIterWithFilter(query, a.filter)
iter := a.sm.Server.IterateAlbums("", query, a.filter)
if a.searchGrid == nil {
a.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), a.im)
a.contr.ConnectAlbumGridActions(a.searchGrid)
@@ -356,7 +352,6 @@ type savedFavoritesPage struct {
pm *backend.PlaybackManager
sm *backend.ServerManager
im *backend.ImageManager
lm *backend.LibraryManager
gridState widgets.GridViewState
searchGridState widgets.GridViewState
filter mediaprovider.AlbumFilter
+13 -12
View File
@@ -2,6 +2,7 @@ package browsing
import (
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/ui/controller"
myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util"
@@ -22,12 +23,12 @@ type GenrePage struct {
contr *controller.Controller
im *backend.ImageManager
pm *backend.PlaybackManager
lm *backend.LibraryManager
mp mediaprovider.MediaProvider
grid *widgets.GridView
searchGrid *widgets.GridView
searcher *widgets.SearchEntry
searchText string
filter backend.AlbumFilter
filter mediaprovider.AlbumFilter
filterBtn *widgets.AlbumFilterButton
titleDisp *widget.RichText
playRandom *widget.Button
@@ -37,13 +38,13 @@ type GenrePage struct {
container *fyne.Container
}
func NewGenrePage(genre string, contr *controller.Controller, pm *backend.PlaybackManager, lm *backend.LibraryManager, im *backend.ImageManager) *GenrePage {
func NewGenrePage(genre string, contr *controller.Controller, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager) *GenrePage {
g := &GenrePage{
genre: genre,
filter: backend.AlbumFilter{Genres: []string{genre}},
filter: mediaprovider.AlbumFilter{Genres: []string{genre}},
contr: contr,
pm: pm,
lm: lm,
mp: mp,
im: im,
}
g.ExtendBaseWidget(g)
@@ -53,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.lm.GenreIter(g.genre, g.filter)
iter := g.mp.IterateAlbums("", "", g.filter)
g.grid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.contr.ConnectAlbumGridActions(g.grid)
g.createSearchAndFilter()
@@ -90,7 +91,7 @@ func restoreGenrePage(saved *savedGenrePage) *GenrePage {
genre: saved.genre,
contr: saved.contr,
pm: saved.pm,
lm: saved.lm,
mp: saved.mp,
im: saved.im,
searchText: saved.searchText,
filter: saved.filter,
@@ -124,7 +125,7 @@ func (g *GenrePage) Reload() {
if g.searchText != "" {
g.doSearch(g.searchText)
} else {
iter := g.lm.GenreIter(g.genre, g.filter)
iter := g.mp.IterateAlbums("", "", g.filter)
g.grid.Reset(widgets.NewGridViewAlbumIterator(iter))
g.grid.Refresh()
}
@@ -137,7 +138,7 @@ func (g *GenrePage) Save() SavedPage {
searchText: g.searchText,
contr: g.contr,
pm: g.pm,
lm: g.lm,
mp: g.mp,
im: g.im,
gridState: g.grid.SaveToState(),
}
@@ -167,7 +168,7 @@ func (g *GenrePage) OnSearched(query string) {
}
func (g *GenrePage) doSearch(query string) {
iter := g.lm.SearchIterWithFilter(query, g.filter)
iter := g.mp.IterateAlbums("", query, g.filter)
if g.searchGrid == nil {
g.searchGrid = widgets.NewGridView(widgets.NewGridViewAlbumIterator(iter), g.im)
g.contr.ConnectAlbumGridActions(g.searchGrid)
@@ -185,10 +186,10 @@ func (g *GenrePage) playRandomSongs() {
type savedGenrePage struct {
genre string
searchText string
filter backend.AlbumFilter
filter mediaprovider.AlbumFilter
contr *controller.Controller
pm *backend.PlaybackManager
lm *backend.LibraryManager
mp mediaprovider.MediaProvider
im *backend.ImageManager
gridState widgets.GridViewState
searchGridState widgets.GridViewState
+2 -3
View File
@@ -2,6 +2,7 @@ package browsing
import (
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/layouts"
@@ -10,8 +11,6 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
type NowPlayingPage struct {
@@ -83,7 +82,7 @@ func (a *NowPlayingPage) SelectAll() {
a.tracklist.SelectAll()
}
func (a *NowPlayingPage) OnSongChange(song *subsonic.Child, lastScrobbledIfAny *subsonic.Child) {
func (a *NowPlayingPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) {
if song == nil {
a.nowPlayingID = ""
} else {
+16 -20
View File
@@ -5,6 +5,7 @@ import (
"log"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller"
@@ -17,8 +18,6 @@ import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
type PlaylistPage struct {
@@ -90,7 +89,7 @@ func (a *PlaylistPage) Route() controller.Route {
return controller.PlaylistRoute(a.playlistID)
}
func (a *PlaylistPage) OnSongChange(song *subsonic.Child, lastScrobbledIfAny *subsonic.Child) {
func (a *PlaylistPage) OnSongChange(song, lastScrobbledIfAny *mediaprovider.Track) {
if song == nil {
a.nowPlayingID = ""
} else {
@@ -119,7 +118,7 @@ func (a *PlaylistPage) load() {
log.Printf("Failed to get playlist: %s", err.Error())
return
}
a.tracklist.Tracks = playlist.Entry
a.tracklist.Tracks = playlist.Tracks
a.tracklist.SetNowPlaying(a.nowPlayingID)
a.tracklist.Refresh()
a.header.Update(playlist)
@@ -148,10 +147,7 @@ func (a *PlaylistPage) doSetNewTrackOrder(op sharedutil.TrackReorderOp) {
for i, tr := range newTracks {
ids[i] = tr.ID
}
err := a.sm.Server.CreatePlaylistWithTracks(ids, map[string]string{
"playlistId": a.playlistID,
})
if err != nil {
if err := a.sm.Server.ReplacePlaylistTracks(a.playlistID, ids); err != nil {
log.Printf("error updating playlist: %s", err.Error())
} else {
a.tracklist.Tracks = newTracks
@@ -161,7 +157,7 @@ func (a *PlaylistPage) doSetNewTrackOrder(op sharedutil.TrackReorderOp) {
}
func (a *PlaylistPage) onRemoveSelectedFromPlaylist() {
a.sm.Server.UpdatePlaylistTracks(a.playlistID, nil, a.tracklist.SelectedTrackIndexes())
a.sm.Server.EditPlaylistTracks(a.playlistID, nil, a.tracklist.SelectedTrackIndexes())
a.tracklist.UnselectAll()
go a.Reload()
}
@@ -170,7 +166,7 @@ type PlaylistPageHeader struct {
widget.BaseWidget
page *PlaylistPage
playlistInfo *subsonic.Playlist
playlistInfo *mediaprovider.PlaylistWithTracks
image *widgets.ImagePlaceholder
editButton *widget.Button
@@ -199,7 +195,7 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader {
a.trackTimeLabel = widget.NewLabel("")
a.editButton = widget.NewButtonWithIcon("Edit", theme.DocumentCreateIcon(), func() {
if a.playlistInfo != nil {
page.contr.DoEditPlaylistWorkflow(a.playlistInfo)
page.contr.DoEditPlaylistWorkflow(&a.playlistInfo.Playlist)
}
})
a.editButton.Hidden = true
@@ -244,18 +240,18 @@ func (a *PlaylistPageHeader) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(a.container)
}
func (a *PlaylistPageHeader) Update(playlist *subsonic.Playlist) {
func (a *PlaylistPageHeader) Update(playlist *mediaprovider.PlaylistWithTracks) {
a.playlistInfo = playlist
a.editButton.Hidden = playlist.Owner != a.page.sm.Server.User
a.editButton.Hidden = playlist.Owner != a.page.sm.LoggedInUser
a.titleLabel.Segments[0].(*widget.TextSegment).Text = playlist.Name
a.descriptionLabel.SetText(playlist.Comment)
a.descriptionLabel.SetText(playlist.Description)
a.ownerLabel.SetText(a.formatPlaylistOwnerStr(playlist))
a.trackTimeLabel.SetText(a.formatPlaylistTrackTimeStr(playlist))
a.createdAtLabel.SetText("created at TODO")
var haveCover bool
if playlist.CoverArt != "" {
if im, err := a.page.im.GetCoverThumbnail(playlist.CoverArt); err == nil && im != nil {
if playlist.CoverArtID != "" {
if im, err := a.page.im.GetCoverThumbnail(playlist.CoverArtID); err == nil && im != nil {
a.image.SetImage(im, false /*tappable*/)
haveCover = true
}
@@ -268,7 +264,7 @@ func (a *PlaylistPageHeader) Update(playlist *subsonic.Playlist) {
a.Refresh()
}
func (a *PlaylistPageHeader) formatPlaylistOwnerStr(p *subsonic.Playlist) string {
func (a *PlaylistPageHeader) formatPlaylistOwnerStr(p *mediaprovider.PlaylistWithTracks) string {
pubPriv := "Public"
if !p.Public {
pubPriv = "Private"
@@ -276,12 +272,12 @@ func (a *PlaylistPageHeader) formatPlaylistOwnerStr(p *subsonic.Playlist) string
return fmt.Sprintf("%s playlist by %s", pubPriv, p.Owner)
}
func (a *PlaylistPageHeader) formatPlaylistTrackTimeStr(p *subsonic.Playlist) string {
func (a *PlaylistPageHeader) formatPlaylistTrackTimeStr(p *mediaprovider.PlaylistWithTracks) string {
tracks := "tracks"
if p.SongCount == 1 {
if p.TrackCount == 1 {
tracks = "track"
}
return fmt.Sprintf("%d %s, %s", p.SongCount, tracks, util.SecondsToTimeString(float64(p.Duration)))
return fmt.Sprintf("%d %s, %s", p.TrackCount, tracks, util.SecondsToTimeString(float64(p.Duration)))
}
func (s *playlistPageState) Restore() Page {
+18 -19
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller"
@@ -18,8 +19,6 @@ import (
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
type PlaylistsPage struct {
@@ -28,8 +27,8 @@ type PlaylistsPage struct {
cfg *backend.PlaylistsPageConfig
contr *controller.Controller
sm *backend.ServerManager
playlists []*subsonic.Playlist
searchedPlaylists []*subsonic.Playlist
playlists []*mediaprovider.Playlist
searchedPlaylists []*mediaprovider.Playlist
viewToggle *widgets.ToggleButtonGroup
searcher *widgets.SearchEntry
@@ -76,7 +75,7 @@ func newPlaylistsPage(contr *controller.Controller, cfg *backend.PlaylistsPageCo
}
func (a *PlaylistsPage) load(searchOnLoad bool) {
playlists, err := a.sm.Server.GetPlaylists(nil)
playlists, err := a.sm.Server.GetPlaylists()
if err != nil {
log.Printf("error loading playlists: %v", err.Error())
}
@@ -93,7 +92,7 @@ func (a *PlaylistsPage) createListView() {
a.listView.OnNavTo = a.showPlaylistPage
}
func (a *PlaylistsPage) createGridView(playlists []*subsonic.Playlist) {
func (a *PlaylistsPage) createGridView(playlists []*mediaprovider.Playlist) {
model := createPlaylistGridViewModel(playlists)
a.gridView = widgets.NewFixedGridView(model, a.contr.App.ImageManager)
a.gridView.OnPlay = func(id string, shuffle bool) {
@@ -110,7 +109,7 @@ func (a *PlaylistsPage) createGridView(playlists []*subsonic.Playlist) {
log.Printf("error loading playlist: %s", err.Error())
return
}
a.contr.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(pl.Entry))
a.contr.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(pl.Tracks))
}()
}
}
@@ -142,17 +141,17 @@ func (a *PlaylistsPage) showGridView() {
a.container.Objects[0].Refresh()
}
func createPlaylistGridViewModel(playlists []*subsonic.Playlist) []widgets.GridViewItemModel {
return sharedutil.MapSlice(playlists, func(pl *subsonic.Playlist) widgets.GridViewItemModel {
func createPlaylistGridViewModel(playlists []*mediaprovider.Playlist) []widgets.GridViewItemModel {
return sharedutil.MapSlice(playlists, func(pl *mediaprovider.Playlist) widgets.GridViewItemModel {
tracks := "tracks"
if pl.SongCount == 1 {
if pl.TrackCount == 1 {
tracks = "track"
}
return widgets.GridViewItemModel{
Name: pl.Name,
ID: pl.ID,
CoverArtID: pl.CoverArt,
Secondary: fmt.Sprintf("%d %s", pl.SongCount, tracks),
CoverArtID: pl.CoverArtID,
Secondary: fmt.Sprintf("%d %s", pl.TrackCount, tracks),
}
})
}
@@ -164,15 +163,15 @@ func (a *PlaylistsPage) showPlaylistPage(id string) {
func (a *PlaylistsPage) onSearched(query string) {
// since the playlist list is returned in full non-paginated, we will do our own
// simple search based on the name, description, and owner, rather than calling a server API
var playlists []*subsonic.Playlist
var playlists []*mediaprovider.Playlist
if query == "" {
a.searchedPlaylists = nil
playlists = a.playlists
} else {
a.searchedPlaylists = sharedutil.FilterSlice(a.playlists, func(p *subsonic.Playlist) bool {
a.searchedPlaylists = sharedutil.FilterSlice(a.playlists, func(p *mediaprovider.Playlist) bool {
qLower := strings.ToLower(query)
return strings.Contains(strings.ToLower(p.Name), qLower) ||
strings.Contains(strings.ToLower(p.Comment), qLower) ||
strings.Contains(strings.ToLower(p.Description), qLower) ||
strings.Contains(strings.ToLower(p.Owner), qLower)
})
playlists = a.searchedPlaylists
@@ -182,7 +181,7 @@ func (a *PlaylistsPage) onSearched(query string) {
// update the model for both views if initialized,
// refresh the active view
func (a *PlaylistsPage) refreshView(playlists []*subsonic.Playlist) {
func (a *PlaylistsPage) refreshView(playlists []*mediaprovider.Playlist) {
if a.listView != nil {
a.listView.Playlists = playlists
}
@@ -247,7 +246,7 @@ func (a *PlaylistsPage) CreateRenderer() fyne.WidgetRenderer {
type PlaylistList struct {
widget.BaseWidget
Playlists []*subsonic.Playlist
Playlists []*mediaprovider.Playlist
OnNavTo func(string)
columnsLayout *layouts.ColumnsLayout
@@ -274,9 +273,9 @@ func NewPlaylistList() *PlaylistList {
row := item.(*PlaylistListRow)
row.ID = a.Playlists[id].ID
row.nameLabel.Text = a.Playlists[id].Name
row.descrptionLabel.Text = a.Playlists[id].Comment
row.descrptionLabel.Text = a.Playlists[id].Description
row.ownerLabel.Text = a.Playlists[id].Owner
row.trackCountLabel.Text = strconv.Itoa(a.Playlists[id].SongCount)
row.trackCountLabel.Text = strconv.Itoa(a.Playlists[id].TrackCount)
row.Refresh()
},
)
+5 -5
View File
@@ -28,17 +28,17 @@ func NewRouter(app *backend.App, controller *controller.Controller, nav Navigati
func (r Router) CreatePage(rte controller.Route) Page {
switch rte.Page {
case controller.Album:
return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager, r.Controller)
return NewAlbumPage(rte.Arg, &r.App.Config.AlbumPage, r.App.PlaybackManager, r.App.ServerManager.Server, r.App.ImageManager, r.Controller)
case controller.Albums:
return NewAlbumsPage(&r.App.Config.AlbumsPage, r.Controller, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager)
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)
case controller.Artists:
return NewArtistsGenresPage(false, r.Controller, r.App.ServerManager)
case controller.Favorites:
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager)
return NewFavoritesPage(&r.App.Config.FavoritesPage, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
case controller.Genre:
return NewGenrePage(rte.Arg, r.Controller, r.App.PlaybackManager, r.App.LibraryManager, r.App.ImageManager)
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)
case controller.NowPlaying:
@@ -48,7 +48,7 @@ func (r Router) CreatePage(rte controller.Route) Page {
case controller.Playlists:
return NewPlaylistsPage(r.Controller, &r.App.Config.PlaylistsPage, r.App.ServerManager)
case controller.Tracks:
return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.LibraryManager)
return NewTracksPage(r.Controller, &r.App.Config.TracksPage, r.App.ServerManager.Server)
}
return nil
}
+8 -9
View File
@@ -2,6 +2,7 @@ package browsing
import (
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller"
"github.com/dweymouth/supersonic/ui/layouts"
@@ -12,8 +13,6 @@ import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
type TracksPage struct {
@@ -37,11 +36,11 @@ type tracksPageState struct {
searchText string
contr *controller.Controller
conf *backend.TracksPageConfig
lm *backend.LibraryManager
mp mediaprovider.MediaProvider
}
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, lm *backend.LibraryManager) *TracksPage {
t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, lm: lm}}
func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig, mp mediaprovider.MediaProvider) *TracksPage {
t := &TracksPage{tracksPageState: tracksPageState{contr: contr, conf: conf, mp: mp}}
t.ExtendBaseWidget(t)
t.tracklist = widgets.NewTracklist(nil)
@@ -79,12 +78,12 @@ func (t *TracksPage) Route() controller.Route {
func (t *TracksPage) Reload() {
t.tracklist.Clear()
iter := t.lm.AllTracksIterator()
iter := t.mp.IterateTracks("")
// loads asynchronously
t.loader = widgets.NewTracklistLoader(t.tracklist, iter)
}
func (t *TracksPage) OnSongChange(track *subsonic.Child, lastScrobbledIfAny *subsonic.Child) {
func (t *TracksPage) OnSongChange(track, lastScrobbledIfAny *mediaprovider.Track) {
t.nowPlayingID = sharedutil.TrackIDOrEmptyStr(track)
t.tracklist.SetNowPlaying(t.nowPlayingID)
if t.searchTracklist != nil {
@@ -130,7 +129,7 @@ func (t *TracksPage) doSearch(query string) {
} else {
t.searchTracklist.Clear()
}
iter := t.lm.SearchTracksIterator(query)
iter := t.mp.IterateTracks(query)
t.searchLoader = widgets.NewTracklistLoader(t.searchTracklist, iter)
t.container.Objects[0].(*fyne.Container).Objects[0] = t.searchTracklist
t.Refresh()
@@ -146,7 +145,7 @@ func (t *TracksPage) Save() SavedPage {
}
func (s *tracksPageState) Restore() Page {
t := NewTracksPage(s.contr, s.conf, s.lm)
t := NewTracksPage(s.contr, s.conf, s.mp)
t.searchText = s.searchText
if t.searchText != "" {
t.searcher.Entry.Text = t.searchText
+19 -55
View File
@@ -3,12 +3,10 @@ package controller
import (
"image"
"log"
"math"
"strconv"
"sync"
"time"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/player"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/dialogs"
@@ -20,8 +18,6 @@ import (
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
type NavigationHandler func(Route)
@@ -94,14 +90,14 @@ func (m *Controller) ShowPopUpImage(img image.Image) {
func (m *Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) {
tracklist.OnAddToPlaylist = m.DoAddTracksToPlaylistWorkflow
tracklist.OnAddToQueue = func(tracks []*subsonic.Child) {
tracklist.OnAddToQueue = func(tracks []*mediaprovider.Track) {
m.App.PlaybackManager.LoadTracks(tracks, true, false)
}
tracklist.OnPlayTrackAt = func(idx int) {
m.App.PlaybackManager.LoadTracks(tracklist.Tracks, false, false)
m.App.PlaybackManager.PlayTrackAt(idx)
}
tracklist.OnPlaySelection = func(tracks []*subsonic.Child, shuffle bool) {
tracklist.OnPlaySelection = func(tracks []*mediaprovider.Track, shuffle bool) {
m.App.PlaybackManager.LoadTracks(tracks, false, shuffle)
m.App.PlaybackManager.PlayFromBeginning()
}
@@ -137,7 +133,7 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) {
log.Printf("error loading album: %s", err.Error())
return
}
m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Song))
m.DoAddTracksToPlaylistWorkflow(sharedutil.TracksToIDs(album.Tracks))
}
}
@@ -173,7 +169,10 @@ 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.LibraryManager.GetUserOwnedPlaylists()
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())
@@ -192,10 +191,9 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
pop.Hide()
m.doModalClosed()
if playlistChoice < 0 {
m.App.ServerManager.Server.CreatePlaylistWithTracks(
trackIDs, map[string]string{"name": newPlaylistName})
m.App.ServerManager.Server.CreatePlaylist(newPlaylistName, trackIDs)
} else {
m.App.ServerManager.Server.UpdatePlaylistTracks(
m.App.ServerManager.Server.EditPlaylistTracks(
pls[playlistChoice].ID, trackIDs, nil /*tracksToRemove*/)
}
}
@@ -203,7 +201,7 @@ func (m *Controller) DoAddTracksToPlaylistWorkflow(trackIDs []string) {
pop.Show()
}
func (m *Controller) DoEditPlaylistWorkflow(playlist *subsonic.Playlist) {
func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) {
dlg := dialogs.NewEditPlaylistDialog(playlist)
pop := widget.NewModalPopUp(dlg, m.MainWindow.Canvas())
m.ClosePopUpOnEscape(pop)
@@ -234,11 +232,7 @@ func (m *Controller) DoEditPlaylistWorkflow(playlist *subsonic.Playlist) {
pop.Hide()
m.doModalClosed()
go func() {
err := m.App.ServerManager.Server.UpdatePlaylist(playlist.ID, map[string]string{
"name": dlg.Name,
"comment": dlg.Description,
"public": strconv.FormatBool(dlg.IsPublic),
})
err := m.App.ServerManager.Server.EditPlaylist(playlist.ID, dlg.Name, dlg.Description, dlg.IsPublic)
if err != nil {
log.Printf("error updating playlist: %s", err.Error())
} else if rte := m.CurPageFunc(); rte.Page == Playlist && rte.Arg == playlist.ID {
@@ -405,49 +399,19 @@ func (c *Controller) doModalClosed() {
}
func (c *Controller) SetTrackFavorites(trackIDs []string, favorite bool) {
s := c.App.ServerManager.Server
if favorite {
go s.Star(subsonic.StarParameters{SongIDs: trackIDs})
} else {
go s.Unstar(subsonic.StarParameters{SongIDs: trackIDs})
}
c.App.ServerManager.Server.SetFavorite(mediaprovider.RatingFavoriteParameters{
TrackIDs: trackIDs,
}, favorite)
for _, id := range trackIDs {
c.App.PlaybackManager.OnTrackFavoriteStatusChanged(id, favorite)
}
}
func (c *Controller) SetTrackRatings(trackIDs []string, rating int) {
// Subsonic doesn't allow bulk setting ratings.
// To not overwhelm the server with requests, set rating for
// only 5 tracks at a time concurrently
batchSize := 5
batchSetRating := func(offs int, wg *sync.WaitGroup) {
for i := 0; i < batchSize && offs+i < len(trackIDs); i++ {
if wg != nil {
wg.Add(1)
}
go func(idx int) {
c.App.ServerManager.Server.SetRating(trackIDs[idx], rating)
if wg != nil {
wg.Done()
}
}(offs + i)
}
}
if len(trackIDs) <= 5 {
// one batch only - no need to use wait group
batchSetRating(0, nil)
} else {
go func() {
numBatches := int(math.Ceil(float64(len(trackIDs)) / float64(batchSize)))
for i := 0; i < numBatches; i++ {
var wg sync.WaitGroup
batchSetRating(i*batchSize, &wg)
wg.Wait()
}
}()
}
c.App.ServerManager.Server.SetRating(mediaprovider.RatingFavoriteParameters{
TrackIDs: trackIDs,
}, rating)
// Notify PlaybackManager of rating change to update
// the in-memory track models
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
"github.com/dweymouth/supersonic/backend/mediaprovider"
)
type EditPlaylistDialog struct {
@@ -23,11 +23,11 @@ type EditPlaylistDialog struct {
container *fyne.Container
}
func NewEditPlaylistDialog(playlist *subsonic.Playlist) *EditPlaylistDialog {
func NewEditPlaylistDialog(playlist *mediaprovider.Playlist) *EditPlaylistDialog {
e := &EditPlaylistDialog{
IsPublic: playlist.Public,
Name: playlist.Name,
Description: playlist.Comment,
Description: playlist.Description,
}
e.ExtendBaseWidget(e)
+30 -9
View File
@@ -6,17 +6,38 @@ import (
"log"
"sync"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/res"
"github.com/dweymouth/supersonic/sharedutil"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/go-subsonic/subsonic"
)
const batchFetchSize = 6
type BatchingIterator struct {
iter mediaprovider.AlbumIterator
}
func NewBatchingIterator(iter mediaprovider.AlbumIterator) BatchingIterator {
return BatchingIterator{iter}
}
func (b *BatchingIterator) NextN(n int) []*mediaprovider.Album {
results := make([]*mediaprovider.Album, 0, n)
i := 0
for i < n {
album := b.iter.Next()
if album == nil {
break
}
results = append(results, album)
i++
}
return results
}
type ImageFetcher interface {
GetCoverThumbnailFromCache(string) (image.Image, bool)
GetCoverThumbnail(string) (image.Image, error)
@@ -27,24 +48,24 @@ type GridViewIterator interface {
}
type gridViewAlbumIterator struct {
iter *backend.BatchingIterator
iter BatchingIterator
}
func (g gridViewAlbumIterator) NextN(n int) []GridViewItemModel {
albums := g.iter.NextN(n)
return sharedutil.MapSlice(albums, func(al *subsonic.AlbumID3) GridViewItemModel {
return sharedutil.MapSlice(albums, func(al *mediaprovider.Album) GridViewItemModel {
return GridViewItemModel{
Name: al.Name,
ID: al.ID,
CoverArtID: al.CoverArt,
Secondary: al.Artist,
SecondaryID: al.ArtistID,
CoverArtID: al.CoverArtID,
Secondary: al.ArtistNames[0],
SecondaryID: al.ArtistIDs[0],
}
})
}
func NewGridViewAlbumIterator(iter backend.AlbumIterator) GridViewIterator {
return gridViewAlbumIterator{iter: backend.NewBatchingIterator(iter)}
func NewGridViewAlbumIterator(iter mediaprovider.AlbumIterator) GridViewIterator {
return gridViewAlbumIterator{iter: NewBatchingIterator(iter)}
}
type GridView struct {