reactivate works through ipc now, at least on Mac

This commit is contained in:
Drew Weymouth
2024-06-12 09:07:15 -07:00
parent e0974fdb03
commit 8e815c644a
6 changed files with 67 additions and 90 deletions
+28 -73
View File
@@ -17,7 +17,6 @@ import (
"github.com/dweymouth/supersonic/backend/player" "github.com/dweymouth/supersonic/backend/player"
"github.com/dweymouth/supersonic/backend/player/mpv" "github.com/dweymouth/supersonic/backend/player/mpv"
"github.com/dweymouth/supersonic/backend/util" "github.com/dweymouth/supersonic/backend/util"
"github.com/fsnotify/fsnotify"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/20after4/configdir" "github.com/20after4/configdir"
@@ -25,13 +24,10 @@ import (
) )
const ( const (
configFile = "config.toml" configFile = "config.toml"
portableDir = "supersonic_portable" portableDir = "supersonic_portable"
sessionDir = "session" savedQueueFile = "saved_queue.json"
sessionLockFile = ".lock" themesDir = "themes"
sessionActivateFile = ".activate"
savedQueueFile = "saved_queue.json"
themesDir = "themes"
) )
var ( var (
@@ -47,6 +43,7 @@ type App struct {
LocalPlayer *mpv.Player LocalPlayer *mpv.Player
UpdateChecker UpdateChecker UpdateChecker UpdateChecker
MPRISHandler *MPRISHandler MPRISHandler *MPRISHandler
ipcServer ipc.IPCServer
// UI callbacks to be set in main // UI callbacks to be set in main
OnReactivate func() OnReactivate func()
@@ -91,24 +88,6 @@ func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string)
return nil, ErrAnotherInstance return nil, ErrAnotherInstance
} }
/*
sessionPath := path.Join(confDir, sessionDir)
if _, err := os.Stat(path.Join(sessionPath, sessionLockFile)); err == nil {
log.Println("Another instance is running. Reactivating it...")
reactivateFile := path.Join(sessionPath, sessionActivateFile)
if f, err := os.Create(reactivateFile); err == nil {
f.Close()
}
time.Sleep(750 * time.Millisecond)
if _, err := os.Stat(reactivateFile); err == nil {
log.Println("No other instance responded. Starting as normal...")
os.RemoveAll(sessionPath)
} else {
return nil, ErrAnotherInstance
}
}
*/
log.Printf("Starting %s...", appName) log.Printf("Starting %s...", appName)
log.Printf("Using config dir: %s", confDir) log.Printf("Using config dir: %s", confDir)
log.Printf("Using cache dir: %s", cacheDir) log.Printf("Using cache dir: %s", cacheDir)
@@ -124,19 +103,6 @@ func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string)
a.readConfig() a.readConfig()
a.startConfigWriter(a.bgrndCtx) a.startConfigWriter(a.bgrndCtx)
/*
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 = NewUpdateChecker(appVersionTag, latestReleaseURL, &a.Config.Application.LastCheckedVersion)
a.UpdateChecker.Start(a.bgrndCtx, 24*time.Hour) a.UpdateChecker.Start(a.bgrndCtx, 24*time.Hour)
@@ -155,9 +121,14 @@ func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string)
a.ServerManager.SetPrefetchAlbumCoverCallback(func(coverID string) { a.ServerManager.SetPrefetchAlbumCoverCallback(func(coverID string) {
_, _ = a.ImageManager.GetCoverThumbnail(coverID) _, _ = a.ImageManager.GetCoverThumbnail(coverID)
}) })
listener, _ := ipc.Listen()
server := ipc.NewServer(a.PlaybackManager, nil) // Start IPC server
go server.Serve(listener) listener, err := ipc.Listen()
if err == nil {
a.ipcServer = ipc.NewServer(a.PlaybackManager, a.callOnReactivate,
func() { _ = a.callOnExit() })
go a.ipcServer.Serve(listener)
}
// OS media center integrations // OS media center integrations
a.setupMPRIS(displayAppName) a.setupMPRIS(displayAppName)
@@ -211,26 +182,6 @@ func (a *App) readConfig() {
a.Config = cfg 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 <-sessionWatch.Events:
activatePath := path.Join(sessionPath, sessionActivateFile)
if _, err := os.Stat(activatePath); err == nil {
os.Remove(path.Join(sessionPath, sessionActivateFile))
a.callOnReactivate()
}
}
}
}()
}
}
// periodically save config file so abnormal exit won't lose settings // periodically save config file so abnormal exit won't lose settings
func (a *App) startConfigWriter(ctx context.Context) { func (a *App) startConfigWriter(ctx context.Context) {
tick := time.NewTicker(2 * time.Minute) tick := time.NewTicker(2 * time.Minute)
@@ -254,6 +205,17 @@ func (a *App) callOnReactivate() {
} }
} }
func (a *App) callOnExit() error {
if a.OnExit == nil {
return errors.New("no quit handler registered")
}
go func() {
time.Sleep(10 * time.Millisecond)
a.OnExit()
}()
return nil
}
func (a *App) initMPV() error { func (a *App) initMPV() error {
p := mpv.NewWithClientName(a.appName) p := mpv.NewWithClientName(a.appName)
c := a.Config.LocalPlayback c := a.Config.LocalPlayback
@@ -329,16 +291,7 @@ func (a *App) setupMPRIS(mprisAppName string) {
return a.ImageManager.GetCoverArtUrl(id) return a.ImageManager.GetCoverArtUrl(id)
} }
a.MPRISHandler.OnRaise = func() error { a.callOnReactivate(); return nil } a.MPRISHandler.OnRaise = func() error { a.callOnReactivate(); return nil }
a.MPRISHandler.OnQuit = func() error { a.MPRISHandler.OnQuit = a.callOnExit
if a.OnExit == nil {
return errors.New("no quit handler registered")
}
go func() {
time.Sleep(10 * time.Millisecond)
a.OnExit()
}()
return nil
}
a.MPRISHandler.Start() a.MPRISHandler.Start()
} }
@@ -361,6 +314,9 @@ func (a *App) DeleteServerCacheDir(serverID uuid.UUID) error {
} }
func (a *App) Shutdown() { func (a *App) Shutdown() {
if a.ipcServer != nil {
a.ipcServer.Shutdown(a.bgrndCtx)
}
a.MPRISHandler.Shutdown() a.MPRISHandler.Shutdown()
a.PlaybackManager.DisableCallbacks() a.PlaybackManager.DisableCallbacks()
if a.Config.Application.SavePlayQueue { if a.Config.Application.SavePlayQueue {
@@ -377,7 +333,6 @@ func (a *App) Shutdown() {
a.cancel() a.cancel()
a.LocalPlayer.Destroy() a.LocalPlayer.Destroy()
a.Config.WriteConfigFile(a.configFilePath()) a.Config.WriteConfigFile(a.configFilePath())
os.RemoveAll(path.Join(a.configDir, sessionDir))
} }
func (a *App) LoadSavedPlayQueue() error { func (a *App) LoadSavedPlayQueue() error {
+1 -6
View File
@@ -17,15 +17,10 @@ type Client struct {
// Connect attempts to connect to the IPC socket as client. // Connect attempts to connect to the IPC socket as client.
func Connect() (*Client, error) { func Connect() (*Client, error) {
conn, err := Dial()
if err != nil {
log.Println("dial error")
return nil, err
}
client := &Client{httpC: http.Client{ client := &Client{httpC: http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return conn, nil return Dial()
}, },
}, },
}} }}
+8 -1
View File
@@ -2,7 +2,10 @@
package ipc package ipc
import "net" import (
"net"
"os"
)
func Dial() (net.Conn, error) { func Dial() (net.Conn, error) {
// TODO - use XDG runtime dir, also handle portable mode // TODO - use XDG runtime dir, also handle portable mode
@@ -12,3 +15,7 @@ func Dial() (net.Conn, error) {
func Listen() (net.Listener, error) { func Listen() (net.Listener, error) {
return net.Listen("unix", "/tmp/supersonic.sock") return net.Listen("unix", "/tmp/supersonic.sock")
} }
func DestroyConn() error {
return os.Remove("/tmp/supersonic.sock")
}
+5
View File
@@ -17,3 +17,8 @@ func Dial() (net.Conn, error) {
func Listen() (net.Listener, error) { func Listen() (net.Listener, error) {
return winio.ListenPipe("supersonic", nil) return winio.ListenPipe("supersonic", nil)
} }
func DestroyConn() error {
// Windows named pipes automatically clean up
return nil
}
+24 -9
View File
@@ -1,7 +1,9 @@
package ipc package ipc
import ( import (
"context"
"encoding/json" "encoding/json"
"net"
"net/http" "net/http"
) )
@@ -17,21 +19,34 @@ type PlaybackHandler interface {
SetVolume(int) error SetVolume(int) error
} }
type WindowHandler interface { type IPCServer interface {
Show() Serve(net.Listener) error
Quit() Shutdown(context.Context) error
} }
type serverImpl struct { type serverImpl struct {
server *http.Server
pbHandler PlaybackHandler pbHandler PlaybackHandler
wdHandler WindowHandler showFn func()
quitFn func()
} }
func NewServer(pbHandler PlaybackHandler, wdHandler WindowHandler) *http.Server { func NewServer(pbHandler PlaybackHandler, showFn, quitFn func()) IPCServer {
s := serverImpl{pbHandler: pbHandler, wdHandler: wdHandler} s := &serverImpl{pbHandler: pbHandler, showFn: showFn, quitFn: quitFn}
return &http.Server{ s.server = &http.Server{
Handler: s.createHandler(), Handler: s.createHandler(),
} }
return s
}
func (s *serverImpl) Serve(listener net.Listener) error {
return s.server.Serve(listener)
}
func (s *serverImpl) Shutdown(ctx context.Context) error {
err := s.server.Shutdown(ctx)
DestroyConn()
return err
} }
func (s *serverImpl) createHandler() http.Handler { func (s *serverImpl) createHandler() http.Handler {
@@ -42,11 +57,11 @@ func (s *serverImpl) createHandler() http.Handler {
}) })
m.HandleFunc(PingPath, s.makeSimpleEndpointHandler(func() error { return nil })) m.HandleFunc(PingPath, s.makeSimpleEndpointHandler(func() error { return nil }))
m.HandleFunc(ShowPath, s.makeSimpleEndpointHandler(func() error { m.HandleFunc(ShowPath, s.makeSimpleEndpointHandler(func() error {
s.wdHandler.Show() s.showFn()
return nil return nil
})) }))
m.HandleFunc(QuitPath, s.makeSimpleEndpointHandler(func() error { m.HandleFunc(QuitPath, s.makeSimpleEndpointHandler(func() error {
go s.wdHandler.Quit() s.quitFn()
return nil return nil
})) }))
m.HandleFunc(PlayPath, s.makeSimpleEndpointHandler(s.pbHandler.Continue)) m.HandleFunc(PlayPath, s.makeSimpleEndpointHandler(s.pbHandler.Continue))
+1 -1
View File
@@ -12,7 +12,6 @@ require (
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee
github.com/dweymouth/go-subsonic v0.0.0-20240603150834-605046e7c78a github.com/dweymouth/go-subsonic v0.0.0-20240603150834-605046e7c78a
github.com/fsnotify/fsnotify v1.7.0
github.com/godbus/dbus/v5 v5.1.0 github.com/godbus/dbus/v5 v5.1.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/pelletier/go-toml/v2 v2.0.8 github.com/pelletier/go-toml/v2 v2.0.8
@@ -30,6 +29,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect github.com/disintegration/imaging v1.6.2 // indirect
github.com/fredbi/uri v1.1.0 // indirect github.com/fredbi/uri v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect github.com/fyne-io/glfw-js v0.0.0-20240101223322-6e1efdc71b7a // indirect
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect