Skip to content

Commit

Permalink
add cmdline flags and some fixes to the IPC layer
Browse files Browse the repository at this point in the history
  • Loading branch information
dweymouth committed Jun 13, 2024
1 parent 8e815c6 commit 280030c
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 91 deletions.
56 changes: 44 additions & 12 deletions backend/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,6 @@ func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string)
configdir.MakePath(confDir)
configdir.MakePath(cacheDir)

cli, err := ipc.Connect()
if err == nil {
log.Println("Another instance is running. Reactivating it...")
cli.Show()
return nil, ErrAnotherInstance
}

log.Printf("Starting %s...", appName)
log.Printf("Using config dir: %s", confDir)
log.Printf("Using cache dir: %s", cacheDir)

a := &App{
appName: appName,
appVersionTag: appVersionTag,
Expand All @@ -101,7 +90,20 @@ func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string)
}
a.bgrndCtx, a.cancel = context.WithCancel(context.Background())
a.readConfig()
a.startConfigWriter(a.bgrndCtx)

if HaveCommandLineOptions() || !a.Config.Application.AllowMultiInstance {
connected, err := a.checkCLIFlagsAndSendIPCMsg()
if err != nil {
log.Printf("error sending IPC message: %s", err.Error())
}
if connected /* we reached the other instance at all */ {
return nil, ErrAnotherInstance
}
}

log.Printf("Starting %s...", appName)
log.Printf("Using config dir: %s", confDir)
log.Printf("Using cache dir: %s", cacheDir)

a.UpdateChecker = NewUpdateChecker(appVersionTag, latestReleaseURL, &a.Config.Application.LastCheckedVersion)
a.UpdateChecker.Start(a.bgrndCtx, 24*time.Hour)
Expand Down Expand Up @@ -137,6 +139,8 @@ func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string)
return a.ImageManager.GetCoverArtUrl(id)
})

a.startConfigWriter(a.bgrndCtx)

return a, nil
}

Expand Down Expand Up @@ -367,6 +371,34 @@ func (a *App) SaveConfigFile() {
a.lastWrittenCfg = *a.Config
}

func (a *App) checkCLIFlagsAndSendIPCMsg() (connected bool, err error) {
cli, err := ipc.Connect()
if err != nil {
return false, err
}

switch {
case *FlagPlay:
err = cli.Play()
case *FlagPause:
err = cli.Pause()
case *FlagPlayPause:
err = cli.PlayPause()
case *FlagPrevious:
err = cli.SeekBackOrPrevious()
case *FlagNext:
err = cli.SeekNext()
case VolumeCLIArg >= 0:
err = cli.SetVolume(VolumeCLIArg)
case SeekToCLIArg >= 0:
err = cli.SeekSeconds(SeekToCLIArg)
default:
log.Println("Another instance is running. Reactivating it...")
err = cli.Show()
}
return true, err
}

func (a *App) configFilePath() string {
return path.Join(a.configDir, configFile)
}
Expand Down
41 changes: 41 additions & 0 deletions backend/cmdlineoptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package backend

import (
"flag"
"strconv"
)

var (
VolumeCLIArg int = -1
SeekToCLIArg float64 = -1

FlagPlay = flag.Bool("play", false, "unpause or begin playback")
FlagPause = flag.Bool("pause", false, "pause playback")
FlagPlayPause = flag.Bool("play-pause", false, "toggle play/pause state")
FlagPrevious = flag.Bool("previous", false, "seek to previous track or beginning of current")
FlagNext = flag.Bool("next", false, "seek to next track")
FlagVersion = flag.Bool("version", false, "print app version and exit")
FlagHelp = flag.Bool("help", false, "print command line options and exit")
)

func init() {
flag.Func("volume", "sets the playback volume (0-100)", func(s string) error {
v, err := strconv.Atoi(s)
VolumeCLIArg = v
return err
})

flag.Func("seek-to", "seeks to the given position in seconds in the current file (0.0 - <trackDur>)", func(s string) error {
v, err := strconv.ParseFloat(s, 64)
SeekToCLIArg = v
return err
})
}

func HaveCommandLineOptions() bool {
visitedAny := false
flag.Visit(func(*flag.Flag) {
visitedAny = true
})
return visitedAny
}
50 changes: 18 additions & 32 deletions backend/ipc/api.go
Original file line number Diff line number Diff line change
@@ -1,43 +1,29 @@
package ipc

const (
// GET
PingPath = "/ping"
import "fmt"

// POST
PlayPath = "/transport/play"
// POST
const (
PingPath = "/ping"
PlayPath = "/transport/play"
PlayPausePath = "/transport/playpause"
// POST
PausePath = "/transport/pause"
// POST
StopPath = "/transport/stop"
// POST
PreviousPath = "/transport/previous"
// POST
NextPath = "/transport/next"
// POST(TimePos)
TimePosPath = "/transport/timepos"
// POST to seek
PlayTrackPath = "/queue/playtrack"
// GET -> Volume
// POST(Volume)
VolumePath = "/volume"

// POST
ShowPath = "/window/show"
// POST
QuitPath = "/window/quit"
PausePath = "/transport/pause"
StopPath = "/transport/stop"
PreviousPath = "/transport/previous"
NextPath = "/transport/next"
TimePosPath = "/transport/timepos" // ?s=<seconds>
VolumePath = "/volume" // ?v=<vol>
ShowPath = "/window/show"
QuitPath = "/window/quit"
)

type TimePos struct {
Seconds float64 `json:"seconds"`
type Response struct {
Error string `json:"error"`
}

type Volume struct {
Volume int `json:"volume"`
func SetVolumePath(vol int) string {
return fmt.Sprintf("%s?v=%d", VolumePath, vol)
}

type Response struct {
Error string `json:"error"`
func SeekToSecondsPath(secs float64) string {
return fmt.Sprintf("%s?s=%0.2f", TimePosPath, secs)
}
36 changes: 18 additions & 18 deletions backend/ipc/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,56 +25,56 @@ func Connect() (*Client, error) {
},
}}
if err := client.Ping(); err != nil {
log.Println("ping error")
return nil, err
}
return client, nil
}

func (c *Client) Ping() error {
if c.makeSimpleRequest(http.MethodGet, PingPath) != nil {
if c.sendRequest(PingPath) != nil {
return ErrPingFail
}
return nil
}

func (c *Client) Play() error {
return c.makeSimpleRequest(http.MethodPost, PlayPath)
return c.sendRequest(PlayPath)
}

func (c *Client) Pause() error {
return c.makeSimpleRequest(http.MethodPost, PausePath)
return c.sendRequest(PausePath)
}

func (c *Client) PlayPause() error {
return c.makeSimpleRequest(http.MethodPost, PlayPausePath)
return c.sendRequest(PlayPausePath)
}

func (c *Client) SeekNext() error {
return c.makeSimpleRequest(http.MethodPost, NextPath)
return c.sendRequest(NextPath)
}

func (c *Client) SeekBackOrPrevious() error {
return c.makeSimpleRequest(http.MethodPost, NextPath)
return c.sendRequest(PreviousPath)
}

func (c *Client) SeekSeconds(secs float64) error {
return c.sendRequest(SeekToSecondsPath(secs))
}

func (c *Client) SetVolume(vol int) error {
return c.sendRequest(SetVolumePath(vol))
}

func (c *Client) Show() error {
return c.makeSimpleRequest(http.MethodPost, ShowPath)
return c.sendRequest(ShowPath)
}

func (c *Client) Quit() error {
return c.makeSimpleRequest(http.MethodPost, QuitPath)
return c.sendRequest(QuitPath)
}

func (c *Client) makeSimpleRequest(method string, path string) error {
var resp *http.Response
var err error
switch method {
case http.MethodGet:
resp, err = c.httpC.Get("http://supersonic/" + path)
case http.MethodPost:
resp, err = c.httpC.Post("http://supersonic/"+path, "application/json", nil)
}
func (c *Client) sendRequest(path string) error {
resp, err := c.httpC.Get("http://supersonic/" + path)

if err != nil {
log.Printf("http err: %v\n", err)
Expand Down
28 changes: 25 additions & 3 deletions backend/ipc/conn_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,41 @@
package ipc

import (
"fmt"
"net"
"os"
"os/user"
"path"
"runtime"
)

var socketPath = "/tmp/supersonic.sock"

func init() {
if runtime.GOOS == "darwin" {
if home, err := os.UserHomeDir(); err == nil {
socketPath = path.Join(home, "Library", "Caches", "supersonic", "supersonic.sock")
} else if user, err := user.Current(); err == nil {
socketPath = fmt.Sprintf("/tmp/supersonic-%s.sock", user.Name)
}
} else {
if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
socketPath = path.Join(runtime, "supersonic.sock")
} else if user, err := user.Current(); err == nil {
socketPath = fmt.Sprintf("/tmp/supersonic-%s.sock", user.Name)
}
}
}

func Dial() (net.Conn, error) {
// TODO - use XDG runtime dir, also handle portable mode
return net.Dial("unix", "/tmp/supersonic.sock")
return net.Dial("unix", socketPath)
}

func Listen() (net.Listener, error) {
return net.Listen("unix", "/tmp/supersonic.sock")
return net.Listen("unix", socketPath)
}

func DestroyConn() error {
return os.Remove("/tmp/supersonic.sock")
return os.Remove(socketPath)
}
22 changes: 9 additions & 13 deletions backend/ipc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"net"
"net/http"
"strconv"
)

type PlaybackHandler interface {
Expand Down Expand Up @@ -71,25 +72,20 @@ func (s *serverImpl) createHandler() http.Handler {
m.HandleFunc(PreviousPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekBackOrPrevious))
m.HandleFunc(NextPath, s.makeSimpleEndpointHandler(s.pbHandler.SeekNext))
m.HandleFunc(TimePosPath, func(w http.ResponseWriter, r *http.Request) {
var t TimePos
if err := json.NewDecoder(r.Response.Body).Decode(&t); err != nil {
_s := r.URL.Query().Get("s")
if secs, err := strconv.ParseFloat(_s, 64); err == nil {
s.writeSimpleResponse(w, s.pbHandler.SeekSeconds(secs))
} else {
s.writeErr(w, err)
return
}
s.writeSimpleResponse(w, s.pbHandler.SeekSeconds(t.Seconds))
})
m.HandleFunc(VolumePath, func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
msg, _ := json.Marshal(Volume{Volume: s.pbHandler.Volume()})
w.Write(msg)
return
}
var v Volume
if err := json.NewDecoder(r.Response.Body).Decode(&v); err != nil {
v := r.URL.Query().Get("v")
if vol, err := strconv.Atoi(v); err == nil {
s.writeSimpleResponse(w, s.pbHandler.SetVolume(vol))
} else {
s.writeErr(w, err)
return
}
s.writeSimpleResponse(w, s.pbHandler.SetVolume(v.Volume))
})
return m
}
Expand Down
Loading

0 comments on commit 280030c

Please sign in to comment.