From 99522a773f842edbd0abcb4726ff688e59a4af1c Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 24 Dec 2022 11:58:24 -0800 Subject: [PATCH] Add config, prompting for server connect on first run --- backend/config.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 4 +++ go.sum | 10 ++++++ main.go | 73 ++++++++++++++++++++++++++++++---------- ui/mainwindow.go | 42 +++++++++++++++++++++++ 5 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 backend/config.go create mode 100644 ui/mainwindow.go diff --git a/backend/config.go b/backend/config.go new file mode 100644 index 0000000..854276e --- /dev/null +++ b/backend/config.go @@ -0,0 +1,85 @@ +package backend + +import ( + "os" + + "github.com/google/uuid" + "github.com/pelletier/go-toml" +) + +type ServerConfig struct { + ID uuid.UUID + Nickname string + Hostname string + Username string + Default bool +} + +type Config struct { + Servers []*ServerConfig +} + +func DefaultConfig() *Config { + return &Config{} +} + +func ReadConfigFile(filepath string) (*Config, error) { + f, err := os.Open(filepath) + if err != nil { + return nil, err + } + defer f.Close() + + c := &Config{} + if err := toml.NewDecoder(f).Decode(c); err != nil { + return nil, err + } + 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, hostname, username string) *ServerConfig { + s := &ServerConfig{ + ID: uuid.New(), + Nickname: nickname, + Hostname: hostname, + Username: username, + } + c.Servers = append(c.Servers, s) + return s +} + +func (c *Config) WriteConfigFile(filepath string) error { + b, err := toml.Marshal(c) + if err != nil { + return err + } + os.WriteFile(filepath, b, 0644) + + return nil +} diff --git a/go.mod b/go.mod index 9b9549e..58a0656 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,16 @@ require ( github.com/20after4/configdir v0.1.1 github.com/bluele/gcache v0.0.2 github.com/dweymouth/go-subsonic v0.0.0-20221214005741-bd8048fa1863 + github.com/google/uuid v1.3.0 github.com/pelletier/go-toml v1.9.3 github.com/wildeyedskies/go-mpv v0.0.0-20221204042335-e8961dc66756 + github.com/zalando/go-keyring v0.2.1 ) require ( fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 // indirect + github.com/alessio/shellescape v1.4.1 // indirect + github.com/danieljoos/wincred v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect diff --git a/go.sum b/go.sum index 3d34b40..712f494 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= +github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -65,6 +67,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -101,6 +105,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6Pl github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -166,6 +171,8 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -267,6 +274,7 @@ github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQO github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -290,6 +298,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zalando/go-keyring v0.2.1 h1:MBRN/Z8H4U5wEKXiD67YbDAr5cj/DOStmSga70/2qKc= +github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= diff --git a/main.go b/main.go index 272a128..b69b7eb 100644 --- a/main.go +++ b/main.go @@ -5,16 +5,17 @@ import ( "fmt" "log" "net/http" + "path" "supersonic/backend" "supersonic/player" "supersonic/ui" - "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/widget" "github.com/20after4/configdir" "github.com/dweymouth/go-subsonic" + "github.com/zalando/go-keyring" ) const ( @@ -22,7 +23,13 @@ const ( configFile = "config.toml" ) +func configPath() string { + return path.Join(configdir.LocalConfig(appname), configFile) +} + func main() { + // TODO: organize this whole file better. Move some stuff to mainwindow? + ctx, cancel := context.WithCancel(context.Background()) p := player.NewWithClientName(appname) @@ -30,33 +37,67 @@ func main() { log.Fatalf("failed to initialize mpv player: %s", err.Error()) } + configdir.MakePath(configdir.LocalConfig(appname)) + + cfg, err := backend.ReadConfigFile(configPath()) + if err != nil { + log.Printf("Error reading app config file: %v", err) + cfg = backend.DefaultConfig() + } + server := cfg.GetDefaultServer() + myApp := app.New() - myWindow := myApp.NewWindow(appname) + mainWindow := ui.NewMainWindow(myApp, appname, p) - b := ui.NewBottomPanel(p) - c := container.NewBorder(nil, b, nil, nil, &layout.Spacer{}) + if server == nil { + d := ui.NewAddServerDialog("Connect to Server") + pop := widget.NewModalPopUp(d, mainWindow.Canvas()) + d.OnSubmit = func() { + pop.Hide() + server = cfg.AddServer(d.Nickname, d.Host, d.Username) + err := keyring.Set(appname, server.ID.String(), d.Password) + if err != nil { + log.Printf("error setting keyring credentials: %v", err) + } + setupServer(ctx, mainWindow, p, server) + } + pop.Show() + } else { + setupServer(ctx, mainWindow, p, server) + } - myWindow.SetContent(c) - myWindow.Resize(fyne.NewSize(1000, 800)) - myWindow.Show() + mainWindow.Show() + myApp.Run() + cfg.WriteConfigFile(configPath()) + + cancel() + p.Destroy() +} + +func setupServer(ctx context.Context, myWindow ui.MainWindow, p *player.Player, server *backend.ServerConfig) { + pass, err := keyring.Get(appname, server.ID.String()) + if err != nil { + log.Printf("error reading keyring credentials: %v", err) + } s := &subsonic.Client{ Client: &http.Client{}, - BaseUrl: "***REMOVED***", - User: "drew", + BaseUrl: server.Hostname, + User: server.Username, ClientName: appname, } - if err := s.Authenticate("***REMOVED***"); err != nil { + if err := s.Authenticate(pass); err != nil { + // TODO: error dialog fmt.Printf("error authenticating: %v\n", err) } lm := backend.NewLibraryManager(s) pm := backend.NewPlaybackManager(ctx, s, p) im := backend.NewImageManager(s, configdir.LocalCache(appname, "covers")) - b.ImageManager = im + myWindow.BottomPanel.ImageManager = im - b.SetPlaybackManager(pm) + myWindow.BottomPanel.SetPlaybackManager(pm) pm.OnSongChange(func(song *subsonic.Child) { if song == nil { myWindow.SetTitle("gomuse") @@ -66,8 +107,6 @@ func main() { }) ag := ui.NewAlbumGrid(lm.RecentlyAddedIter(), pm, im) - myWindow.SetContent(container.NewBorder(nil, b, nil, nil, ag)) - myApp.Run() - cancel() - p.Destroy() + myWindow.SetContent(container.NewBorder(nil, myWindow.BottomPanel, nil, nil, ag)) + } diff --git a/ui/mainwindow.go b/ui/mainwindow.go new file mode 100644 index 0000000..160180c --- /dev/null +++ b/ui/mainwindow.go @@ -0,0 +1,42 @@ +package ui + +import ( + "supersonic/player" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" +) + +type MainWindow struct { + Window fyne.Window + + BottomPanel *BottomPanel +} + +func NewMainWindow(app fyne.App, appName string, p *player.Player) MainWindow { + m := MainWindow{ + Window: app.NewWindow(appName), + BottomPanel: NewBottomPanel(p), + } + c := container.NewBorder(nil, m.BottomPanel, nil, nil, &layout.Spacer{}) + m.Window.SetContent(c) + m.Window.Resize(fyne.NewSize(1000, 800)) + return m +} + +func (m *MainWindow) Show() { + m.Window.Show() +} + +func (m *MainWindow) Canvas() fyne.Canvas { + return m.Window.Canvas() +} + +func (m *MainWindow) SetTitle(title string) { + m.Window.SetTitle(title) +} + +func (m *MainWindow) SetContent(c fyne.CanvasObject) { + m.Window.SetContent(c) +}