add cmdline flags and some fixes to the IPC layer
This commit is contained in:
+44
-12
@@ -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,
|
||||
@@ -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)
|
||||
@@ -137,6 +139,8 @@ func StartupApp(appName, displayAppName, appVersionTag, latestReleaseURL string)
|
||||
return a.ImageManager.GetCoverArtUrl(id)
|
||||
})
|
||||
|
||||
a.startConfigWriter(a.bgrndCtx)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+20
-34
@@ -1,43 +1,29 @@
|
||||
package ipc
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
// GET
|
||||
PingPath = "/ping"
|
||||
|
||||
// POST
|
||||
PlayPath = "/transport/play"
|
||||
// POST
|
||||
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 Volume struct {
|
||||
Volume int `json:"volume"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func SetVolumePath(vol int) string {
|
||||
return fmt.Sprintf("%s?v=%d", VolumePath, vol)
|
||||
}
|
||||
|
||||
func SeekToSecondsPath(secs float64) string {
|
||||
return fmt.Sprintf("%s?s=%0.2f", TimePosPath, secs)
|
||||
}
|
||||
|
||||
+18
-18
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+9
-13
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type PlaybackHandler interface {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user