reactivate works through ipc now, at least on Mac
This commit is contained in:
+28
-73
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user