From 20e4d8c5f2d44f6dee8bcb0aae510e6f4f436ba2 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 17 Jun 2023 17:00:47 -0700 Subject: [PATCH] Fix #193: exit and reactivate existing instance if another instance running --- backend/app.go | 56 ++++++++++++++++++++++++++++++++++++++++++++--- backend/config.go | 2 ++ go.mod | 2 +- go.sum | 2 -- main.go | 1 + 5 files changed, 57 insertions(+), 6 deletions(-) diff --git a/backend/app.go b/backend/app.go index 758aa6a..f71a8bd 100644 --- a/backend/app.go +++ b/backend/app.go @@ -12,14 +12,22 @@ import ( "github.com/dweymouth/supersonic/backend/util" "github.com/dweymouth/supersonic/player" "github.com/dweymouth/supersonic/sharedutil" + "github.com/fsnotify/fsnotify" "github.com/google/uuid" "github.com/20after4/configdir" "github.com/zalando/go-keyring" ) +const ( + sessionDir = "session" + sessionLockFile = ".lock" + sessionActivateFile = ".activate" +) + var ( - ErrNoServers = errors.New("no servers set up") + ErrNoServers = errors.New("no servers set up") + ErrAnotherInstance = errors.New("another instance is running") ) type App struct { @@ -29,6 +37,7 @@ type App struct { PlaybackManager *PlaybackManager Player *player.Player UpdateChecker UpdateChecker + OnReactivate func() appName string appVersionTag string @@ -42,15 +51,34 @@ func (a *App) VersionTag() string { } func StartupApp(appName, appVersionTag, configFile, latestReleaseURL string) (*App, error) { - a := &App{appName: appName, appVersionTag: appVersionTag, configFile: configFile} - a.bgrndCtx, a.cancel = context.WithCancel(context.Background()) + sessionPath := configdir.LocalConfig(appName, sessionDir) + if _, err := os.Stat(path.Join(sessionPath, sessionLockFile)); err == nil { + log.Println("Another instance is running. Reactivating it and exiting...") + if f, err := os.Create(path.Join(sessionPath, sessionActivateFile)); err == nil { + f.Close() + } + return nil, ErrAnotherInstance + } log.Printf("Starting %s...", appName) log.Printf("Using config dir: %s", configdir.LocalConfig(appName)) log.Printf("Using cache dir: %s", configdir.LocalCache(appName)) + a := &App{appName: appName, appVersionTag: appVersionTag, configFile: configFile} + a.bgrndCtx, a.cancel = context.WithCancel(context.Background()) a.readConfig() + if !a.Config.Application.AllowMultiInstance { + log.Println("Creating session lock file") + os.MkdirAll(sessionPath, 0770) + if f, err := os.Create(path.Join(sessionPath, sessionLockFile)); err == nil { + f.Close() + } else { + log.Printf("error creating session file: %s", err.Error()) + } + a.startSessionWatcher(sessionPath) + } + a.UpdateChecker = NewUpdateChecker(appVersionTag, latestReleaseURL, &a.Config.Application.LastCheckedVersion) a.UpdateChecker.Start(a.bgrndCtx, 24*time.Hour) @@ -87,6 +115,27 @@ func (a *App) readConfig() { a.Config = cfg } +func (a *App) startSessionWatcher(sessionPath string) { + if sessionWatch, err := fsnotify.NewWatcher(); err == nil { + sessionWatch.Add(sessionPath) + go func() { + for { + select { + case <-a.bgrndCtx.Done(): + return + case event, ok := <-sessionWatch.Events: + if ok && event.Op == fsnotify.Create && path.Base(event.Name) == sessionActivateFile { + os.Remove(path.Join(sessionPath, sessionActivateFile)) + if a.OnReactivate != nil { + a.OnReactivate() + } + } + } + } + }() + } +} + func (a *App) initMPV() error { p := player.NewWithClientName(a.appName) c := a.Config.LocalPlayback @@ -163,6 +212,7 @@ func (a *App) Shutdown() { a.cancel() a.Player.Destroy() a.Config.WriteConfigFile(a.configPath()) + os.RemoveAll(configdir.LocalConfig(a.appName, sessionDir)) } func (a *App) configPath() string { diff --git a/backend/config.go b/backend/config.go index 3d9d318..891a3fe 100644 --- a/backend/config.go +++ b/backend/config.go @@ -28,6 +28,7 @@ type AppConfig struct { EnableSystemTray bool CloseToSystemTray bool StartupPage string + AllowMultiInstance bool // Experimental - may be removed in future FontNormalTTF string @@ -119,6 +120,7 @@ func DefaultConfig(appVersionTag string) *Config { EnableSystemTray: true, CloseToSystemTray: false, StartupPage: "Albums", + AllowMultiInstance: false, }, AlbumPage: AlbumPageConfig{ TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite", "Rating"}, diff --git a/go.mod b/go.mod index 128866d..1eea570 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/20after4/configdir v0.1.1 github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee github.com/dweymouth/go-subsonic v0.0.0-20230614154319-792d18c75fb4 + github.com/fsnotify/fsnotify v1.5.4 github.com/google/uuid v1.3.0 github.com/pelletier/go-toml/v2 v2.0.8 github.com/zalando/go-keyring v0.2.1 @@ -20,7 +21,6 @@ require ( github.com/danieljoos/wincred v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fredbi/uri v0.1.0 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect diff --git a/go.sum b/go.sum index b13d885..a905e4b 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,6 @@ github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230505012127-ca61c153b2a5 h1:uXbHzg9 github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20230505012127-ca61c153b2a5/go.mod h1:X2+NrR+62mvAiAt2fwKT7035zQsE77KVV1NlvWo4vW8= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0= -github.com/dweymouth/go-subsonic v0.0.0-20230513020020-0790f53c2868 h1:403Dden/cdQyDM8ydonHLxTBPjgQXZ76PUfmBG92+Dw= -github.com/dweymouth/go-subsonic v0.0.0-20230513020020-0790f53c2868/go.mod h1:fUez6NFiEJiQTZizZ1BThZr5GJXAbigzGYjEPNm4tdI= github.com/dweymouth/go-subsonic v0.0.0-20230614154319-792d18c75fb4 h1:cuyvB4GjTMBHKJwMJ/LFpuKfSVXuCheNj6fgxBa3m1Y= github.com/dweymouth/go-subsonic v0.0.0-20230614154319-792d18c75fb4/go.mod h1:fUez6NFiEJiQTZizZ1BThZr5GJXAbigzGYjEPNm4tdI= github.com/eclipse/paho.mqtt.golang v1.3.5/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc= diff --git a/main.go b/main.go index b2ab4c5..63dfbe3 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ func main() { h = 800 } mainWindow := ui.NewMainWindow(fyneApp, displayName, appVersion, myApp, fyne.NewSize(w, h)) + myApp.OnReactivate = mainWindow.Show go func() { // TODO: There is a race condition with laying out the window before the