From 8fcd7f98b6b5eed4054152c2956ccffd131e25de Mon Sep 17 00:00:00 2001 From: "Sean E. Russell" Date: Mon, 30 Dec 2024 06:34:58 -0600 Subject: [PATCH] change: removes pro-active background loading of playlist data, making the playlist page act more like the other pages: loading subcontent on demand change: playlist lists (such as in the "add song to playlist" function) can now be more accurate. They were being populated once at GUI initialization, and so would always miss newly added playlists. --- gui.go | 4 -- page_browser.go | 18 +++-- page_playlist.go | 141 +++++++++------------------------------ page_queue.go | 8 +-- subsonic/connection.go | 21 +----- widget_selectplaylist.go | 10 ++- 6 files changed, 57 insertions(+), 145 deletions(-) diff --git a/gui.go b/gui.go index 09495f8..ed2553d 100644 --- a/gui.go +++ b/gui.go @@ -56,7 +56,6 @@ type Ui struct { mpvEvents chan mpvplayer.UiEvent mprisPlayer *remote.MprisPlayer - playlists []subsonic.Playlist connection *subsonic.Connection player *mpvplayer.Player logger *logger.Logger @@ -91,7 +90,6 @@ func InitGui(artists []subsonic.Artist, eventLoop: nil, // initialized by initEventLoops() mpvEvents: make(chan mpvplayer.UiEvent, 5), - playlists: []subsonic.Playlist{}, connection: connection, player: player, logger: logger, @@ -203,8 +201,6 @@ func InitGui(artists []subsonic.Artist, SetFocus(rootFlex). EnableMouse(true) - ui.playlistPage.UpdatePlaylists() - return ui } diff --git a/page_browser.go b/page_browser.go index e3ba26c..665418d 100644 --- a/page_browser.go +++ b/page_browser.go @@ -170,10 +170,6 @@ func (ui *Ui) createBrowserPage(artists []subsonic.Artist) *BrowserPage { } }) - // "add to playlist" modal - for _, playlist := range ui.playlists { - ui.addToPlaylistList.AddItem(tview.Escape(playlist.Name), "", 0, nil) - } ui.addToPlaylistList.SetBorder(true). SetTitle("Add to Playlist") @@ -190,7 +186,7 @@ func (ui *Ui) createBrowserPage(artists []subsonic.Artist) *BrowserPage { ui.app.SetFocus(browserPage.entityList) return nil } else if event.Key() == tcell.KeyEnter { - playlist := ui.playlists[ui.addToPlaylistList.GetCurrentItem()] + playlist := ui.playlistPage.playlists[ui.addToPlaylistList.GetCurrentItem()] browserPage.handleAddEntityToPlaylist(&playlist) ui.pages.HidePage(PageAddToPlaylist) @@ -217,6 +213,7 @@ func (ui *Ui) createBrowserPage(artists []subsonic.Artist) *BrowserPage { case 'A': // only makes sense to add to a playlist if there are playlists if ui.playlistPage.GetCount() > 0 { + browserPage.updatePlaylists() ui.pages.ShowPage(PageAddToPlaylist) ui.app.SetFocus(ui.addToPlaylistList) } else { @@ -630,3 +627,14 @@ func (b *BrowserPage) handleAddEntityToPlaylist(playlist *subsonic.Playlist) { } }, b.ui.playlistPage.UpdatePlaylists) } + +func (b *BrowserPage) updatePlaylists() { + ui := b.ui + ui.addToPlaylistList.Clear() + if ui.playlistPage.playlists != nil && len(ui.playlistPage.playlists) > 0 { + // "add to playlist" modal + for _, playlist := range ui.playlistPage.playlists { + ui.addToPlaylistList.AddItem(tview.Escape(playlist.Name), "", 0, nil) + } + } +} diff --git a/page_playlist.go b/page_playlist.go index 55f7725..dfc965d 100644 --- a/page_playlist.go +++ b/page_playlist.go @@ -4,15 +4,10 @@ package main import ( - "fmt" - "sync" - "time" - "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "github.com/spezifisch/stmps/logger" "github.com/spezifisch/stmps/subsonic" - "github.com/spf13/viper" ) type PlaylistPage struct { @@ -23,20 +18,18 @@ type PlaylistPage struct { playlistList *tview.List newPlaylistInput *tview.InputField selectedPlaylist *tview.List + playlists []subsonic.Playlist // external refs ui *Ui logger logger.LoggerInterface - - updatingMutex sync.Locker - isUpdating bool } func (ui *Ui) createPlaylistPage() *PlaylistPage { playlistPage := PlaylistPage{ - ui: ui, - logger: ui.logger, - updatingMutex: &sync.Mutex{}, + ui: ui, + logger: ui.logger, + playlists: make([]subsonic.Playlist, 0), } // left half: playlists @@ -48,10 +41,7 @@ func (ui *Ui) createPlaylistPage() *PlaylistPage { SetTitleAlign(tview.AlignLeft). SetBorder(true) - // add the playlists - for _, playlist := range ui.playlists { - playlistPage.playlistList.AddItem(tview.Escape(playlist.Name), "", 0, nil) - } + playlistPage.UpdatePlaylists() // right half: songs of selected playlist playlistPage.selectedPlaylist = tview.NewList(). @@ -167,15 +157,15 @@ func (ui *Ui) createPlaylistPage() *PlaylistPage { playlistPage.DeletePlaylistModal = makeModal(deletePlaylistFlex, 20, 3) playlistPage.playlistList.SetChangedFunc(func(index int, _ string, _ string, _ rune) { - if index < 0 || index >= len(ui.playlists) { + if index < 0 || index >= len(playlistPage.playlists) { return } - playlistPage.handlePlaylistSelected(ui.playlists[index]) + playlistPage.handlePlaylistSelected(playlistPage.playlists[index]) }) // open first playlist by default so we don't get stuck when there's only one playlist - if len(ui.playlists) > 0 { - playlistPage.handlePlaylistSelected(ui.playlists[0]) + if len(playlistPage.playlists) > 0 { + playlistPage.handlePlaylistSelected(playlistPage.playlists[0]) } return &playlistPage @@ -190,94 +180,20 @@ func (p *PlaylistPage) GetCount() int { } func (p *PlaylistPage) UpdatePlaylists() { - // There's a potential race condition here and, albeit highly unlikely to ever get hit, - // we'll put in some protection - p.updatingMutex.Lock() - defer p.updatingMutex.Unlock() - if p.isUpdating { + playlists, err := p.ui.connection.GetPlaylists() + if err != nil { + p.logger.PrintError("GetPlaylists", err) return } - p.isUpdating = true - // TODO (B) Stop pro-actively deeply loading the playlists. This will remove all of the threading and spinner code. - - var spinnerText []rune = []rune(viper.GetString("ui.spinner")) - if len(spinnerText) == 0 { - spinnerText = []rune("▉▊▋▌▍▎▏▎▍▌▋▊▉") + p.playlistList.Clear() + p.playlists = playlists.Playlists + for _, pl := range playlists.Playlists { + p.playlistList.AddItem(tview.Escape(pl.Name), "", 0, nil) } - spinnerMax := len(spinnerText) - 1 - playlistsButton := buttonOrder[PAGE_PLAYLISTS] - stop := make(chan bool) - go func() { - var idx int - timer := time.NewTicker(500 * time.Millisecond) - defer timer.Stop() - for { - select { - case <-timer.C: - p.ui.app.QueueUpdateDraw(func() { - var format string - if playlistsButton == p.ui.menuWidget.activeButton { - format = "%d: [::b][red]%c[white]%s[::-]" - } else { - format = "%d: [red]%c[white]%s" - } - label := fmt.Sprintf(format, PAGE_PLAYLISTS+1, spinnerText[idx], playlistsButton) - p.ui.menuWidget.buttons[playlistsButton].SetLabel(label) - idx++ - if idx > spinnerMax { - idx = 0 - } - }) - case <-stop: - p.ui.app.QueueUpdateDraw(func() { - var format string - if playlistsButton == p.ui.menuWidget.activeButton { - format = "%d: [::b]%s[::-]" - } else { - format = "%d: %s" - } - label := fmt.Sprintf(format, PAGE_PLAYLISTS+1, playlistsButton) - p.ui.menuWidget.buttons[playlistsButton].SetLabel(label) - }) - close(stop) - return - } - } - }() - - go func() { - playlists, err := p.ui.connection.GetPlaylists() - if err != nil { - p.logger.PrintError("GetPlaylists", err) - p.isUpdating = false - stop <- true - return - } - if len(playlists.Playlists) == 0 { - p.logger.Printf("no error from GetPlaylists, but also no response!") - stop <- true - return - } - p.updatingMutex.Lock() - defer p.updatingMutex.Unlock() - p.ui.playlists = playlists.Playlists - p.ui.app.QueueUpdateDraw(func() { - p.playlistList.Clear() - p.ui.addToPlaylistList.Clear() - - for _, playlist := range p.ui.playlists { - p.addPlaylist(playlist) - } - - p.isUpdating = false - }) - stop <- true - }() } func (p *PlaylistPage) addPlaylist(playlist subsonic.Playlist) { - p.playlistList.AddItem(tview.Escape(playlist.Name), "", 0, nil) - p.ui.addToPlaylistList.AddItem(tview.Escape(playlist.Name), "", 0, nil) + // Rather than getting the current selected, let's use the features of closures. } func (p *PlaylistPage) handleAddPlaylistSongToQueue() { @@ -289,7 +205,7 @@ func (p *PlaylistPage) handleAddPlaylistSongToQueue() { if entityIndex < 0 || entityIndex >= p.selectedPlaylist.GetItemCount() { return } - if playlistIndex >= len(p.ui.playlists) || entityIndex >= len(p.ui.playlists[playlistIndex].Entries) { + if playlistIndex >= len(p.playlists) || entityIndex >= len(p.playlists[playlistIndex].Entries) { return } @@ -298,7 +214,7 @@ func (p *PlaylistPage) handleAddPlaylistSongToQueue() { p.selectedPlaylist.SetCurrentItem(entityIndex + 1) } - entity := p.ui.playlists[playlistIndex].Entries[entityIndex] + entity := p.playlists[playlistIndex].Entries[entityIndex] p.ui.addSongToQueue(entity) p.ui.queuePage.UpdateQueue() @@ -306,7 +222,7 @@ func (p *PlaylistPage) handleAddPlaylistSongToQueue() { func (p *PlaylistPage) handleAddPlaylistToQueue() { currentIndex := p.playlistList.GetCurrentItem() - if currentIndex < 0 || currentIndex >= p.playlistList.GetItemCount() || currentIndex >= len(p.ui.playlists) { + if currentIndex < 0 || currentIndex >= p.playlistList.GetItemCount() || currentIndex >= len(p.playlists) { return } @@ -315,7 +231,7 @@ func (p *PlaylistPage) handleAddPlaylistToQueue() { p.playlistList.SetCurrentItem(currentIndex + 1) } - playlist := p.ui.playlists[currentIndex] + playlist := p.playlists[currentIndex] for _, entity := range playlist.Entries { p.ui.addSongToQueue(entity) } @@ -324,6 +240,13 @@ func (p *PlaylistPage) handleAddPlaylistToQueue() { } func (p *PlaylistPage) handlePlaylistSelected(playlist subsonic.Playlist) { + var err error + playlist, err = p.ui.connection.GetPlaylist(string(playlist.Id)) + if err != nil { + p.logger.PrintError("handlePlaylistSelected", err) + return + } + p.selectedPlaylist.Clear() p.selectedPlaylist.SetSelectedFocusOnly(true) @@ -341,25 +264,25 @@ func (p *PlaylistPage) newPlaylist(name string) { return } - p.ui.playlists = append(p.ui.playlists, playlist) + p.playlists = append(p.playlists, playlist) p.playlistList.AddItem(tview.Escape(playlist.Name), "", 0, nil) p.ui.addToPlaylistList.AddItem(tview.Escape(playlist.Name), "", 0, nil) } func (p *PlaylistPage) deletePlaylist(index int) { - if index < 0 || index >= len(p.ui.playlists) { + if index < 0 || index >= len(p.playlists) { return } - playlist := p.ui.playlists[index] + playlist := p.playlists[index] if index == 0 { p.playlistList.SetCurrentItem(1) } // Removes item with specified index - p.ui.playlists = append(p.ui.playlists[:index], p.ui.playlists[index+1:]...) + p.playlists = append(p.playlists[:index], p.playlists[index+1:]...) p.playlistList.RemoveItem(index) p.ui.addToPlaylistList.RemoveItem(index) diff --git a/page_queue.go b/page_queue.go index f0c68bf..e1e19bb 100644 --- a/page_queue.go +++ b/page_queue.go @@ -452,7 +452,7 @@ func (q *QueuePage) saveQueue(playlistName string) { } var playlistId string - for _, p := range q.ui.playlists { + for _, p := range q.ui.playlistPage.playlists { if p.Name == playlistName { playlistId = string(p.Id) break @@ -473,15 +473,15 @@ func (q *QueuePage) saveQueue(playlistName string) { q.logger.Print(message) } else { if playlistId != "" { - for i, pl := range q.ui.playlists { + for i, pl := range q.ui.playlistPage.playlists { if string(pl.Id) == playlistId { - q.ui.playlists[i] = response + q.ui.playlistPage.playlists[i] = response break } } } else { q.ui.playlistPage.addPlaylist(response) - q.ui.playlists = append(q.ui.playlists, response) + q.ui.playlistPage.playlists = append(q.ui.playlistPage.playlists, response) } q.ui.playlistPage.handlePlaylistSelected(response) } diff --git a/subsonic/connection.go b/subsonic/connection.go index bdf242a..a061c66 100644 --- a/subsonic/connection.go +++ b/subsonic/connection.go @@ -381,26 +381,7 @@ func (connection *Connection) GetPlaylists() (Playlists, error) { if resp == nil { return Playlists{}, fmt.Errorf("GetPlaylists nil response from server: %s", err) } - playlists := resp.Playlists - - for i := 0; i < len(playlists.Playlists); i++ { - playlist := playlists.Playlists[i] - - if playlist.SongCount == 0 { - continue - } - - pl, err := connection.GetPlaylist(string(playlist.Id)) - - if err != nil { - return Playlists{Playlists: make([]Playlist, 0)}, err - } - - playlists.Playlists[i].Entries = pl.Entries - - } - - return playlists, nil + return resp.Playlists, nil } func (connection *Connection) GetPlaylist(id string) (Playlist, error) { diff --git a/widget_selectplaylist.go b/widget_selectplaylist.go index 94ec356..a95c2f8 100644 --- a/widget_selectplaylist.go +++ b/widget_selectplaylist.go @@ -41,9 +41,13 @@ func (ui *Ui) createPlaylistSelectionWidget() (m *PlaylistSelectionWidget) { m.accept = tview.NewButton("Accept").SetLabelColor(tcell.ColorBlack) m.cancel = tview.NewButton("Cancel").SetLabelColor(tcell.ColorBlack) m.inputField = tview.NewInputField().SetAutocompleteFunc(func(current string) []string { + // if the playlists page hasn't been created, there's nothing to work with + if ui.playlistPage == nil { + return []string{} + } rv := make([]string, 0) var exactMatch bool - for _, p := range ui.playlists { + for _, p := range ui.playlistPage.playlists { if strings.Contains(p.Name, current) { rv = append(rv, p.Name) } @@ -83,7 +87,7 @@ func (ui *Ui) createPlaylistSelectionWidget() (m *PlaylistSelectionWidget) { acceptFunc := func() { inputText := m.inputField.GetText() if !m.overwrite.IsChecked() { - for _, p := range ui.playlists { + for _, p := range ui.playlistPage.playlists { if p.Name == inputText { return } @@ -188,7 +192,7 @@ func (m *PlaylistSelectionWidget) focusNext(event *tcell.EventKey) *tcell.EventK case m.inputField: st := m.inputField.GetText() found := false - for _, p := range m.ui.playlists { + for _, p := range m.ui.playlistPage.playlists { if p.Name == st { m.overwrite.SetDisabled(false) m.overwriteEnabled = true