From 85eb790cd1ae16a9544dfb5631953a7820ec5589 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 17 May 2023 15:03:31 -0700 Subject: [PATCH 1/5] add multi-server functionality pt. 1 - add server --- backend/app.go | 2 +- backend/servermanager.go | 10 ++++++---- ui/controller/controller.go | 28 ++++++++++++++++++++++++++-- ui/dialogs/logindialog.go | 29 +++++++++++++++++++++++++---- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/backend/app.go b/backend/app.go index 2de52aa..59997e5 100644 --- a/backend/app.go +++ b/backend/app.go @@ -60,7 +60,7 @@ func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*A return nil, err } - a.ServerManager = NewServerManager(appName) + a.ServerManager = NewServerManager(appName, a.Config) a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player, &a.Config.Scrobbling) a.ImageManager = NewImageManager(a.bgrndCtx, a.ServerManager, configdir.LocalCache(a.appName)) a.ServerManager.SetPrefetchAlbumCoverCallback(func(coverID string) { diff --git a/backend/servermanager.go b/backend/servermanager.go index 0cc44b0..4dd3749 100644 --- a/backend/servermanager.go +++ b/backend/servermanager.go @@ -20,14 +20,15 @@ type ServerManager struct { prefetchCoverCB func(string) appName string + config *Config onServerConnected []func() onLogout []func() } var ErrUnreachable = errors.New("server is unreachable") -func NewServerManager(appName string) *ServerManager { - return &ServerManager{appName: appName} +func NewServerManager(appName string, config *Config) *ServerManager { + return &ServerManager{appName: appName, config: config} } func (s *ServerManager) SetPrefetchAlbumCoverCallback(cb func(string)) { @@ -46,6 +47,7 @@ func (s *ServerManager) ConnectToServer(conf *ServerConfig, password string) err s.Server.SetPrefetchCoverCallback(s.prefetchCoverCB) s.LoggedInUser = conf.Username s.ServerID = conf.ID + s.config.SetDefaultServer(s.ServerID) for _, cb := range s.onServerConnected { cb() } @@ -144,8 +146,8 @@ func (s *ServerManager) OnLogout(cb func()) { s.onLogout = append(s.onLogout, cb) } -func (s *ServerManager) GetServerPassword(server *ServerConfig) (string, error) { - return keyring.Get(s.appName, server.ID.String()) +func (s *ServerManager) GetServerPassword(serverID uuid.UUID) (string, error) { + return keyring.Get(s.appName, serverID.String()) } func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) error { diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 52c7665..b597c9d 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -250,7 +250,7 @@ func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) { } func (c *Controller) DoConnectToServerWorkflow(server *backend.ServerConfig) { - pass, err := c.App.ServerManager.GetServerPassword(server) + pass, err := c.App.ServerManager.GetServerPassword(server.ID) if err != nil { log.Printf("error getting password from keyring: %v", err) c.PromptForLoginAndConnect() @@ -269,7 +269,7 @@ func (c *Controller) DoConnectToServerWorkflow(server *backend.ServerConfig) { func (m *Controller) PromptForLoginAndConnect() { // TODO: this will need to be rewritten a bit when we support multi servers // need to make sure the intended server is first in the list passed to NewLoginDialog - d := dialogs.NewLoginDialog(m.App.Config.Servers) + d := dialogs.NewLoginDialog(m.App.Config.Servers, m.App.ServerManager.GetServerPassword) pop := widget.NewModalPopUp(d, m.MainWindow.Canvas()) d.OnSubmit = func(server *backend.ServerConfig, password string) { d.DisableSubmit() @@ -311,6 +311,30 @@ func (m *Controller) PromptForLoginAndConnect() { } editPop.Show() } + d.OnNewServer = func() { + pop.Hide() + newD := dialogs.NewAddEditServerDialog("Add server", nil, m.MainWindow.Canvas().Focus) + newPop := widget.NewModalPopUp(newD, m.MainWindow.Canvas()) + newD.OnSubmit = func() { + d.DisableSubmit() + go func() { + if m.testConnectionAndUpdateDialogText(newD) { + newPop.Hide() + conn := backend.ServerConnection{ + Hostname: newD.Host, + AltHostname: newD.AltHost, + Username: newD.Username, + LegacyAuth: newD.LegacyAuth, + } + server := m.App.Config.AddServer(newD.Nickname, conn) + m.trySetPasswordAndConnectToServer(server, newD.Password) + m.doModalClosed() + } + d.EnableSubmit() + }() + } + newPop.Show() + } m.haveModal = true pop.Show() } diff --git a/ui/dialogs/logindialog.go b/ui/dialogs/logindialog.go index 9ef4a1f..292f9e7 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/google/uuid" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -10,11 +11,14 @@ import ( "fyne.io/fyne/v2/widget" ) +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() servers []*backend.ServerConfig serverSelect *widget.Select @@ -27,7 +31,7 @@ type LoginDialog struct { var _ fyne.Widget = (*LoginDialog)(nil) -func NewLoginDialog(servers []*backend.ServerConfig) *LoginDialog { +func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc) *LoginDialog { l := &LoginDialog{servers: servers} l.ExtendBaseWidget(l) titleLabel := widget.NewLabel("Login to Server") @@ -36,9 +40,20 @@ func NewLoginDialog(servers []*backend.ServerConfig) *LoginDialog { for i, s := range servers { serverNames[i] = s.Nickname } - l.serverSelect = widget.NewSelect(serverNames, func(_ string) {}) + l.serverSelect = widget.NewSelect(serverNames, func(_ string) { + if l.passField == nil { + return + } + if pwFetch != nil { + if pw, err := pwFetch(servers[l.serverSelect.SelectedIndex()].ID); err == nil { + l.passField.SetText(pw) + return + } + } + }) l.serverSelect.SetSelectedIndex(0) editBtn := widget.NewButtonWithIcon("", theme.DocumentCreateIcon(), l.onEditServer) + newBtn := widget.NewButtonWithIcon("", theme.ContentAddIcon(), l.onNewServer) l.passField = widget.NewPasswordEntry() l.passField.OnSubmitted = func(_ string) { l.onSubmit() } l.submitBtn = widget.NewButton("OK", l.onSubmit) @@ -51,7 +66,7 @@ func NewLoginDialog(servers []*backend.ServerConfig) *LoginDialog { container.NewHBox(layout.NewSpacer(), titleLabel, layout.NewSpacer()), container.New(layout.NewFormLayout(), widget.NewLabel("Server"), - container.NewBorder(nil, nil, nil, editBtn, l.serverSelect), + container.NewBorder(nil, nil, nil, container.NewHBox(editBtn, newBtn), l.serverSelect), widget.NewLabel("Password"), l.passField), widget.NewSeparator(), @@ -96,7 +111,7 @@ func (l *LoginDialog) CreateRenderer() fyne.WidgetRenderer { func (l *LoginDialog) MinSize() fyne.Size { l.ExtendBaseWidget(l) - return fyne.NewSize(300, l.container.MinSize().Height) + return fyne.NewSize(330, l.container.MinSize().Height) } func (l *LoginDialog) onSubmit() { @@ -110,3 +125,9 @@ func (l *LoginDialog) onEditServer() { l.OnEditServer(l.servers[l.serverSelect.SelectedIndex()]) } } + +func (l *LoginDialog) onNewServer() { + if l.OnNewServer != nil { + l.OnNewServer() + } +} From 4d4b14e4decf2eeafb30e991f21b029bb360d109 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 17 May 2023 16:19:24 -0700 Subject: [PATCH 2/5] 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]) + } +} From 2bc7680361a01f0b89f42fc16c7ac9bfb33e91ca Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 17 May 2023 17:46:26 -0700 Subject: [PATCH 3/5] multi servers part 3: switch servers menu action + some refactoring --- backend/servermanager.go | 113 ++++++++++++++++++++++++++---------- main.go | 2 +- ui/controller/controller.go | 6 +- ui/dialogs/logindialog.go | 11 ++-- ui/mainwindow.go | 3 +- 5 files changed, 94 insertions(+), 41 deletions(-) diff --git a/backend/servermanager.go b/backend/servermanager.go index 4dd3749..8a6ac5c 100644 --- a/backend/servermanager.go +++ b/backend/servermanager.go @@ -73,6 +73,89 @@ func (s *ServerManager) TestConnectionAndAuth( } } +func (s *ServerManager) GetDefaultServer() *ServerConfig { + for _, s := range s.config.Servers { + if s.Default { + return s + } + } + if len(s.config.Servers) > 0 { + return s.config.Servers[0] + } + return nil +} + +func (s *ServerManager) SetDefaultServer(serverID uuid.UUID) { + var found bool + for _, s := range s.config.Servers { + f := s.ID == serverID + if f { + found = true + } + s.Default = f + } + if !found && len(s.config.Servers) > 0 { + s.config.Servers[0].Default = true + } +} + +func (s *ServerManager) AddServer(nickname string, connection ServerConnection) *ServerConfig { + sc := &ServerConfig{ + ID: uuid.New(), + Nickname: nickname, + ServerConnection: connection, + } + s.config.Servers = append(s.config.Servers, sc) + return sc +} + +func (s *ServerManager) DeleteServer(serverID uuid.UUID) { + s.deleteServerPassword(serverID) + newServers := make([]*ServerConfig, 0, len(s.config.Servers)-1) + for _, s := range s.config.Servers { + if s.ID != serverID { + newServers = append(newServers, s) + } + } + s.config.Servers = newServers +} + +func (s *ServerManager) Logout(deletePassword bool) { + if s.Server != nil { + if deletePassword { + s.deleteServerPassword(s.ServerID) + } + for _, cb := range s.onLogout { + cb() + } + s.Server = nil + s.LoggedInUser = "" + s.ServerID = uuid.UUID{} + } +} + +func (s *ServerManager) deleteServerPassword(serverID uuid.UUID) { + keyring.Delete(s.appName, s.ServerID.String()) +} + +// Sets a callback that is invoked when a server is connected to. +func (s *ServerManager) OnServerConnected(cb func()) { + s.onServerConnected = append(s.onServerConnected, cb) +} + +// Sets a callback that is invoked when the user logs out of a server. +func (s *ServerManager) OnLogout(cb func()) { + s.onLogout = append(s.onLogout, cb) +} + +func (s *ServerManager) GetServerPassword(serverID uuid.UUID) (string, error) { + return keyring.Get(s.appName, serverID.String()) +} + +func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) error { + return keyring.Set(s.appName, server.ID.String(), password) +} + func (s *ServerManager) testConnectionAndCreateClient(connection ServerConnection, password string) (*subsonic.Client, error) { cli, err := s.connect(connection, password) if err != nil { @@ -123,33 +206,3 @@ func (s *ServerManager) connect(connection ServerConnection, password string) (* return cli, nil } } - -func (s *ServerManager) Logout() { - if s.Server != nil { - keyring.Delete(s.appName, s.ServerID.String()) - for _, cb := range s.onLogout { - cb() - } - s.Server = nil - s.LoggedInUser = "" - s.ServerID = uuid.UUID{} - } -} - -// Sets a callback that is invoked when a server is connected to. -func (s *ServerManager) OnServerConnected(cb func()) { - s.onServerConnected = append(s.onServerConnected, cb) -} - -// Sets a callback that is invoked when the user logs out of a server. -func (s *ServerManager) OnLogout(cb func()) { - s.onLogout = append(s.onLogout, cb) -} - -func (s *ServerManager) GetServerPassword(serverID uuid.UUID) (string, error) { - return keyring.Get(s.appName, serverID.String()) -} - -func (s *ServerManager) SetServerPassword(server *ServerConfig, password string) error { - return keyring.Set(s.appName, server.ID.String(), password) -} diff --git a/main.go b/main.go index 770e322..41525ee 100644 --- a/main.go +++ b/main.go @@ -48,7 +48,7 @@ func main() { if runtime.GOOS == "linux" { time.Sleep(250 * time.Millisecond) } - defaultServer := myApp.Config.GetDefaultServer() + defaultServer := myApp.ServerManager.GetDefaultServer() if defaultServer == nil { mainWindow.Controller.PromptForFirstServer() } else { diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 6ccfded..e340cfc 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -156,7 +156,7 @@ func (m *Controller) PromptForFirstServer() { Username: d.Username, LegacyAuth: d.LegacyAuth, } - server := m.App.Config.AddServer(d.Nickname, conn) + server := m.App.ServerManager.AddServer(d.Nickname, conn) if err := m.trySetPasswordAndConnectToServer(server, d.Password); err != nil { log.Printf("error connecting to server: %s", err.Error()) } @@ -332,7 +332,7 @@ func (m *Controller) PromptForLoginAndConnect() { Username: newD.Username, LegacyAuth: newD.LegacyAuth, } - server := m.App.Config.AddServer(newD.Nickname, conn) + server := m.App.ServerManager.AddServer(newD.Nickname, conn) m.trySetPasswordAndConnectToServer(server, newD.Password) m.doModalClosed() } @@ -351,7 +351,7 @@ func (m *Controller) PromptForLoginAndConnect() { 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.ServerManager.DeleteServer(server.ID) m.App.DeleteServerCacheDir(server.ID) d.SetServers(m.App.Config.Servers) } diff --git a/ui/dialogs/logindialog.go b/ui/dialogs/logindialog.go index db750a2..6d083cd 100644 --- a/ui/dialogs/logindialog.go +++ b/ui/dialogs/logindialog.go @@ -39,27 +39,26 @@ func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc) l.ExtendBaseWidget(l) titleLabel := widget.NewLabel("Login to Server") titleLabel.TextStyle.Bold = true + l.passField = widget.NewPasswordEntry() + l.passField.OnSubmitted = func(_ string) { l.onSubmit() } + serverNames := sharedutil.MapSlice(servers, func(s *backend.ServerConfig) string { return s.Nickname }) l.serverSelect = widget.NewSelect(serverNames, func(_ string) { - if l.passField == nil { - return - } if pwFetch != nil { if pw, err := pwFetch(servers[l.serverSelect.SelectedIndex()].ID); err == nil { l.passField.SetText(pw) return } } + l.passField.SetText("") }) 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 l.promptText.Hidden = true diff --git a/ui/mainwindow.go b/ui/mainwindow.go index 03baf35..10832db 100644 --- a/ui/mainwindow.go +++ b/ui/mainwindow.go @@ -113,7 +113,8 @@ func NewMainWindow(fyneApp fyne.App, appName, appVersion string, app *backend.Ap m.BrowsingPane.ClearHistory() m.Controller.PromptForLoginAndConnect() }) - m.BrowsingPane.AddSettingsMenuItem("Log Out", app.ServerManager.Logout) + m.BrowsingPane.AddSettingsMenuItem("Log Out", func() { app.ServerManager.Logout(true) }) + m.BrowsingPane.AddSettingsMenuItem("Switch Servers", func() { app.ServerManager.Logout(false) }) m.BrowsingPane.AddSettingsMenuItem("Check for Updates", func() { go func() { if t := app.UpdateChecker.CheckLatestVersionTag(); t != "" && t != app.VersionTag() { From facf59a6607a53a572ca19a007a390d1088b0509 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 17 May 2023 17:48:37 -0700 Subject: [PATCH 4/5] forgot to delete moved functinos --- backend/config.go | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/backend/config.go b/backend/config.go index 37add76..3a210c1 100644 --- a/backend/config.go +++ b/backend/config.go @@ -183,52 +183,6 @@ func ReadConfigFile(filepath, appVersionTag string) (*Config, error) { return c, nil } -func (c *Config) GetDefaultServer() *ServerConfig { - for _, s := range c.Servers { - if s.Default { - return s - } - } - if len(c.Servers) > 0 { - return c.Servers[0] - } - return nil -} - -func (c *Config) SetDefaultServer(serverID uuid.UUID) { - var found bool - for _, s := range c.Servers { - f := s.ID == serverID - if f { - found = true - } - s.Default = f - } - if !found && len(c.Servers) > 0 { - c.Servers[0].Default = true - } -} - -func (c *Config) AddServer(nickname string, connection ServerConnection) *ServerConfig { - s := &ServerConfig{ - ID: uuid.New(), - Nickname: nickname, - ServerConnection: connection, - } - c.Servers = append(c.Servers, s) - 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 { From c517c40c22bb69ccd864b984483f3cfe33a0635d Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 17 May 2023 17:49:25 -0700 Subject: [PATCH 5/5] finish refactor cleanup --- backend/app.go | 2 +- backend/servermanager.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app.go b/backend/app.go index afd26e6..758aa6a 100644 --- a/backend/app.go +++ b/backend/app.go @@ -139,7 +139,7 @@ func (a *App) setupMPV() error { } func (a *App) LoginToDefaultServer(string) error { - serverCfg := a.Config.GetDefaultServer() + serverCfg := a.ServerManager.GetDefaultServer() if serverCfg == nil { return ErrNoServers } diff --git a/backend/servermanager.go b/backend/servermanager.go index 8a6ac5c..ffa8037 100644 --- a/backend/servermanager.go +++ b/backend/servermanager.go @@ -47,7 +47,7 @@ func (s *ServerManager) ConnectToServer(conf *ServerConfig, password string) err s.Server.SetPrefetchCoverCallback(s.prefetchCoverCB) s.LoggedInUser = conf.Username s.ServerID = conf.ID - s.config.SetDefaultServer(s.ServerID) + s.SetDefaultServer(s.ServerID) for _, cb := range s.onServerConnected { cb() }