From 4d4b14e4decf2eeafb30e991f21b029bb360d109 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 17 May 2023 16:19:24 -0700 Subject: [PATCH] add multi-server functionality pt. 2 - delete server --- backend/app.go | 7 ++++++ backend/config.go | 10 +++++++++ backend/imagemanager.go | 13 +++++++++++ ui/controller/controller.go | 34 ++++++++++++++++++++++++++--- ui/dialogs/addeditserverdialog.go | 29 ++++++++++++++++++++----- ui/dialogs/addtoplaylistdialog.go | 1 + ui/dialogs/editplaylistdialog.go | 1 + ui/dialogs/logindialog.go | 36 ++++++++++++++++++++++--------- 8 files changed, 113 insertions(+), 18 deletions(-) diff --git a/backend/app.go b/backend/app.go index 59997e5..afd26e6 100644 --- a/backend/app.go +++ b/backend/app.go @@ -12,6 +12,7 @@ import ( "github.com/dweymouth/supersonic/backend/util" "github.com/dweymouth/supersonic/player" "github.com/dweymouth/supersonic/sharedutil" + "github.com/google/uuid" "github.com/20after4/configdir" "github.com/zalando/go-keyring" @@ -149,6 +150,12 @@ func (a *App) LoginToDefaultServer(string) error { return a.ServerManager.ConnectToServer(serverCfg, pass) } +func (a *App) DeleteServerCacheDir(serverID uuid.UUID) error { + path := path.Join(configdir.LocalCache(a.appName), serverID.String()) + log.Printf("Deleting server cache dir: %s", path) + return os.RemoveAll(path) +} + func (a *App) Shutdown() { a.PlaybackManager.DisableCallbacks() a.Player.Stop() // will trigger scrobble check diff --git a/backend/config.go b/backend/config.go index b071fd2..37add76 100644 --- a/backend/config.go +++ b/backend/config.go @@ -219,6 +219,16 @@ func (c *Config) AddServer(nickname string, connection ServerConnection) *Server return s } +func (c *Config) DeleteServer(uuid uuid.UUID) { + newServers := make([]*ServerConfig, 0, len(c.Servers)-1) + for _, s := range c.Servers { + if s.ID != uuid { + newServers = append(newServers, s) + } + } + c.Servers = newServers +} + func (c *Config) WriteConfigFile(filepath string) error { b, err := toml.Marshal(c) if err != nil { diff --git a/backend/imagemanager.go b/backend/imagemanager.go index 9644e5a..c515f90 100644 --- a/backend/imagemanager.go +++ b/backend/imagemanager.go @@ -3,6 +3,7 @@ package backend import ( "bytes" "context" + "errors" "fmt" "image" "image/jpeg" @@ -14,6 +15,7 @@ import ( "fyne.io/fyne/v2" "github.com/20after4/configdir" + "github.com/google/uuid" ) const CachedImageValidTime = 24 * time.Hour @@ -74,6 +76,9 @@ func (i *ImageManager) GetFullSizeCoverArt(coverID string) (image.Image, error) if i.cachedFullSizeCoverID == coverID { return i.cachedFullSizeCover, nil } + if i.s.Server == nil { + return nil, errors.New("logged out") + } im, err := i.s.Server.GetCoverArt(coverID, 0) if err != nil { return nil, err @@ -105,6 +110,11 @@ func (i *ImageManager) RefreshCachedArtistImageIfExpired(artistID string, imgURL } func (i *ImageManager) ensureCoverCacheDir() string { + // if user logged out with pending fetches in progress, + // make sure we don't write to nil (00000000-*0) cache directory + if i.s.ServerID == uuid.Nil { + return "" + } path := path.Join(i.baseCacheDir, i.s.ServerID.String(), "covers") configdir.MakePath(path) return path @@ -145,6 +155,9 @@ func (i *ImageManager) fetchAndCacheCoverFromDiskOrServer(coverID string, ttl ti } func (i *ImageManager) fetchAndCacheCoverFromServer(coverID string, ttl time.Duration) (image.Image, error) { + if i.s.Server == nil { + return nil, errors.New("logged out") + } img, err := i.s.Server.GetCoverArt(coverID, coverArtThumbnailSize) if err != nil { return nil, err diff --git a/ui/controller/controller.go b/ui/controller/controller.go index b597c9d..6ccfded 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -1,6 +1,7 @@ package controller import ( + "fmt" "image" "log" "time" @@ -140,7 +141,7 @@ func (m *Controller) ConnectAlbumGridActions(grid *widgets.GridView) { } func (m *Controller) PromptForFirstServer() { - d := dialogs.NewAddEditServerDialog("Connect to Server", nil, m.MainWindow.Canvas().Focus) + d := dialogs.NewAddEditServerDialog("Connect to Server", false, nil, m.MainWindow.Canvas().Focus) pop := widget.NewModalPopUp(d, m.MainWindow.Canvas()) d.OnSubmit = func() { d.DisableSubmit() @@ -290,7 +291,7 @@ func (m *Controller) PromptForLoginAndConnect() { } d.OnEditServer = func(server *backend.ServerConfig) { pop.Hide() - editD := dialogs.NewAddEditServerDialog("Edit server", server, m.MainWindow.Canvas().Focus) + editD := dialogs.NewAddEditServerDialog("Edit server", true, server, m.MainWindow.Canvas().Focus) editPop := widget.NewModalPopUp(editD, m.MainWindow.Canvas()) editD.OnSubmit = func() { d.DisableSubmit() @@ -309,16 +310,21 @@ func (m *Controller) PromptForLoginAndConnect() { d.EnableSubmit() }() } + editD.OnCancel = func() { + editPop.Hide() + pop.Show() + } editPop.Show() } d.OnNewServer = func() { pop.Hide() - newD := dialogs.NewAddEditServerDialog("Add server", nil, m.MainWindow.Canvas().Focus) + newD := dialogs.NewAddEditServerDialog("Add server", true, nil, m.MainWindow.Canvas().Focus) newPop := widget.NewModalPopUp(newD, m.MainWindow.Canvas()) newD.OnSubmit = func() { d.DisableSubmit() go func() { if m.testConnectionAndUpdateDialogText(newD) { + // connection is good newPop.Hide() conn := backend.ServerConnection{ Hostname: newD.Host, @@ -333,8 +339,30 @@ func (m *Controller) PromptForLoginAndConnect() { d.EnableSubmit() }() } + newD.OnCancel = func() { + newPop.Hide() + pop.Show() + } newPop.Show() } + d.OnDeleteServer = func(server *backend.ServerConfig) { + pop.Hide() + dialog.ShowConfirm("Confirm delete server", + fmt.Sprintf("Are you sure you want to delete the server %q?", server.Nickname), + func(ok bool) { + if ok { + m.App.Config.DeleteServer(server.ID) + m.App.DeleteServerCacheDir(server.ID) + d.SetServers(m.App.Config.Servers) + } + if len(m.App.Config.Servers) == 0 { + m.PromptForFirstServer() + } else { + pop.Show() + } + }, m.MainWindow) + + } m.haveModal = true pop.Show() } diff --git a/ui/dialogs/addeditserverdialog.go b/ui/dialogs/addeditserverdialog.go index 89a99ca..e6b8c9f 100644 --- a/ui/dialogs/addeditserverdialog.go +++ b/ui/dialogs/addeditserverdialog.go @@ -21,6 +21,7 @@ type AddEditServerDialog struct { Password string LegacyAuth bool OnSubmit func() + OnCancel func() passField *widget.Entry submitBtn *widget.Button @@ -30,7 +31,7 @@ type AddEditServerDialog struct { var _ fyne.Widget = (*AddEditServerDialog)(nil) -func NewAddEditServerDialog(title string, prefillServer *backend.ServerConfig, focusHandler func(fyne.Focusable)) *AddEditServerDialog { +func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backend.ServerConfig, focusHandler func(fyne.Focusable)) *AddEditServerDialog { a := &AddEditServerDialog{} a.ExtendBaseWidget(a) if prefillServer != nil { @@ -57,11 +58,26 @@ func NewAddEditServerDialog(title string, prefillServer *backend.ServerConfig, f nickField.SetPlaceHolder("My Server") nickField.OnSubmitted = func(_ string) { focusHandler(hostField) } a.submitBtn = widget.NewButton("Enter", a.doSubmit) + a.submitBtn.Importance = widget.HighImportance a.promptText = widget.NewRichTextWithText("") a.promptText.Hidden = true legacyAuthCheck := widget.NewCheckWithData("Use legacy authentication", binding.BindBool(&a.LegacyAuth)) + var bottomRow *fyne.Container + if cancelable { + bottomRow = container.NewHBox( + a.promptText, + layout.NewSpacer(), + widget.NewButton("Cancel", a.onCancel), + a.submitBtn) + } else { + bottomRow = container.NewHBox( + a.promptText, + layout.NewSpacer(), + a.submitBtn) + } + a.container = container.NewVBox( container.NewHBox(layout.NewSpacer(), titleLabel, layout.NewSpacer()), container.New(layout.NewFormLayout(), @@ -78,10 +94,7 @@ func NewAddEditServerDialog(title string, prefillServer *backend.ServerConfig, f ), container.NewHBox(layout.NewSpacer(), legacyAuthCheck), widget.NewSeparator(), - container.NewHBox( - a.promptText, - layout.NewSpacer(), - a.submitBtn), + bottomRow, ) return a } @@ -111,6 +124,12 @@ func (a *AddEditServerDialog) doSubmit() { } } +func (a *AddEditServerDialog) onCancel() { + if a.OnCancel != nil { + a.OnCancel() + } +} + func (a *AddEditServerDialog) doSetPromptText(text string, color fyne.ThemeColorName) { ts := a.promptText.Segments[0].(*widget.TextSegment) ts.Text = text diff --git a/ui/dialogs/addtoplaylistdialog.go b/ui/dialogs/addtoplaylistdialog.go index 11e235c..89329ba 100644 --- a/ui/dialogs/addtoplaylistdialog.go +++ b/ui/dialogs/addtoplaylistdialog.go @@ -48,6 +48,7 @@ func NewAddToPlaylistDialog(title string, existingPlaylistNames []string) *AddTo a.newPlaylistLabel.Hidden = true a.okBtn = widget.NewButton("OK", a.onOK) + a.okBtn.Importance = widget.HighImportance a.okBtn.Disable() cancelBtn := widget.NewButton("Cancel", a.onCancel) diff --git a/ui/dialogs/editplaylistdialog.go b/ui/dialogs/editplaylistdialog.go index 43a3a74..f9492e0 100644 --- a/ui/dialogs/editplaylistdialog.go +++ b/ui/dialogs/editplaylistdialog.go @@ -44,6 +44,7 @@ func NewEditPlaylistDialog(playlist *mediaprovider.Playlist) *EditPlaylistDialog e.OnUpdateMetadata() } }) + submitBtn.Importance = widget.HighImportance cancelBtn := widget.NewButton("Cancel", func() { if e.OnCanceled != nil { e.OnCanceled() diff --git a/ui/dialogs/logindialog.go b/ui/dialogs/logindialog.go index 292f9e7..db750a2 100644 --- a/ui/dialogs/logindialog.go +++ b/ui/dialogs/logindialog.go @@ -2,6 +2,7 @@ package dialogs import ( "github.com/dweymouth/supersonic/backend" + "github.com/dweymouth/supersonic/sharedutil" "github.com/google/uuid" "fyne.io/fyne/v2" @@ -16,11 +17,13 @@ type PasswordFetchFunc func(serverID uuid.UUID) (string, error) type LoginDialog struct { widget.BaseWidget - OnSubmit func(server *backend.ServerConfig, password string) - OnEditServer func(server *backend.ServerConfig) - OnNewServer func() + OnSubmit func(server *backend.ServerConfig, password string) + OnEditServer func(server *backend.ServerConfig) + OnDeleteServer func(server *backend.ServerConfig) + OnNewServer func() + + servers []*backend.ServerConfig - servers []*backend.ServerConfig serverSelect *widget.Select passField *widget.Entry promptText *widget.RichText @@ -36,10 +39,7 @@ func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc) l.ExtendBaseWidget(l) titleLabel := widget.NewLabel("Login to Server") titleLabel.TextStyle.Bold = true - serverNames := make([]string, len(servers)) - for i, s := range servers { - serverNames[i] = s.Nickname - } + serverNames := sharedutil.MapSlice(servers, func(s *backend.ServerConfig) string { return s.Nickname }) l.serverSelect = widget.NewSelect(serverNames, func(_ string) { if l.passField == nil { return @@ -54,9 +54,11 @@ func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc) l.serverSelect.SetSelectedIndex(0) editBtn := widget.NewButtonWithIcon("", theme.DocumentCreateIcon(), l.onEditServer) newBtn := widget.NewButtonWithIcon("", theme.ContentAddIcon(), l.onNewServer) + deleteBtn := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() { l.onDeleteServer(l.serverSelect.SelectedIndex()) }) l.passField = widget.NewPasswordEntry() l.passField.OnSubmitted = func(_ string) { l.onSubmit() } l.submitBtn = widget.NewButton("OK", l.onSubmit) + l.submitBtn.Importance = widget.HighImportance l.promptText = widget.NewRichTextWithText("") l.promptText.Segments[0].(*widget.TextSegment).Style.ColorName = theme.ColorNameError @@ -66,7 +68,7 @@ func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc) container.NewHBox(layout.NewSpacer(), titleLabel, layout.NewSpacer()), container.New(layout.NewFormLayout(), widget.NewLabel("Server"), - container.NewBorder(nil, nil, nil, container.NewHBox(editBtn, newBtn), l.serverSelect), + container.NewBorder(nil, nil, nil, container.NewHBox(editBtn, newBtn, deleteBtn), l.serverSelect), widget.NewLabel("Password"), l.passField), widget.NewSeparator(), @@ -83,6 +85,14 @@ func (l *LoginDialog) SetErrorText(text string) { l.doSetPromptText(text, theme.ColorNameError) } +func (l *LoginDialog) SetServers(servers []*backend.ServerConfig) { + l.servers = servers + l.serverSelect.Options = sharedutil.MapSlice(servers, func(s *backend.ServerConfig) string { return s.Nickname }) + if len(servers) > 0 { + l.serverSelect.SetSelectedIndex(0) + } +} + func (l *LoginDialog) EnableSubmit() { l.submitBtn.Enable() l.submitBtn.Refresh() @@ -111,7 +121,7 @@ func (l *LoginDialog) CreateRenderer() fyne.WidgetRenderer { func (l *LoginDialog) MinSize() fyne.Size { l.ExtendBaseWidget(l) - return fyne.NewSize(330, l.container.MinSize().Height) + return fyne.NewSize(375, l.container.MinSize().Height) } func (l *LoginDialog) onSubmit() { @@ -131,3 +141,9 @@ func (l *LoginDialog) onNewServer() { l.OnNewServer() } } + +func (l *LoginDialog) onDeleteServer(idx int) { + if l.OnDeleteServer != nil { + l.OnDeleteServer(l.servers[idx]) + } +}