Add search CLI commands
This commit is contained in:
+22
-1
@@ -192,7 +192,10 @@ func StartupApp(appName, displayAppName, appVersion, appVersionTag, latestReleas
|
||||
ipc.DestroyConn() // cleanup socket possibly orphaned by crashed process
|
||||
listener, err := ipc.Listen()
|
||||
if err == nil {
|
||||
a.ipcServer = ipc.NewServer(a.PlaybackManager, a.callOnReactivate,
|
||||
a.ipcServer = ipc.NewServer(
|
||||
a.PlaybackManager,
|
||||
&a.ServerManager.Server,
|
||||
a.callOnReactivate,
|
||||
func() { _ = a.callOnExit() })
|
||||
go a.ipcServer.Serve(listener)
|
||||
} else {
|
||||
@@ -564,6 +567,24 @@ func (a *App) checkFlagsAndSendIPCMsg(cli *ipc.Client) error {
|
||||
return cli.PlayPlaylist(PlayPlaylistCLIArg, FirstTrackCLIArg, *FlagShuffle)
|
||||
case PlayTrackCLIArg != "":
|
||||
return cli.PlayTrack(PlayTrackCLIArg)
|
||||
case SearchAlbumCLIArg != "":
|
||||
data, err := cli.SearchAlbum(SearchAlbumCLIArg)
|
||||
if err == nil {
|
||||
fmt.Println(data)
|
||||
}
|
||||
return err
|
||||
case SearchPlaylistCLIArg != "":
|
||||
data, err := cli.SearchPlaylist(SearchPlaylistCLIArg)
|
||||
if err == nil {
|
||||
fmt.Println(data)
|
||||
}
|
||||
return err
|
||||
case SearchTrackCLIArg != "":
|
||||
data, err := cli.SearchTrack(SearchTrackCLIArg)
|
||||
if err == nil {
|
||||
fmt.Println(data)
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,14 +7,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
VolumeCLIArg int = -1
|
||||
SeekToCLIArg float64 = -1
|
||||
SeekByCLIArg float64 = 0
|
||||
VolumePctCLIArg float64 = 0
|
||||
PlayAlbumCLIArg string = ""
|
||||
PlayPlaylistCLIArg string = ""
|
||||
PlayTrackCLIArg string = ""
|
||||
FirstTrackCLIArg int = 0
|
||||
VolumeCLIArg int = -1
|
||||
SeekToCLIArg float64 = -1
|
||||
SeekByCLIArg float64 = 0
|
||||
VolumePctCLIArg float64 = 0
|
||||
PlayAlbumCLIArg string = ""
|
||||
PlayPlaylistCLIArg string = ""
|
||||
PlayTrackCLIArg string = ""
|
||||
FirstTrackCLIArg int = 0
|
||||
SearchAlbumCLIArg string = ""
|
||||
SearchPlaylistCLIArg string = ""
|
||||
SearchTrackCLIArg string = ""
|
||||
|
||||
FlagPlay = flag.Bool("play", false, "unpause or begin playback")
|
||||
FlagPause = flag.Bool("pause", false, "pause playback")
|
||||
@@ -71,6 +74,19 @@ func init() {
|
||||
FirstTrackCLIArg = v
|
||||
return err
|
||||
})
|
||||
|
||||
flag.Func("search-album", "search album", func(s string) error {
|
||||
SearchAlbumCLIArg = s
|
||||
return nil
|
||||
})
|
||||
flag.Func("search-playlist", "search playlist", func(s string) error {
|
||||
SearchPlaylistCLIArg = s
|
||||
return nil
|
||||
})
|
||||
flag.Func("search-track", "search track", func(s string) error {
|
||||
SearchTrackCLIArg = s
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func HaveCommandLineOptions() bool {
|
||||
|
||||
+41
-18
@@ -1,28 +1,36 @@
|
||||
package ipc
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
PingPath = "/ping"
|
||||
PlayPath = "/transport/play"
|
||||
PlayAlbumPath = "/transport/play-album" // ?id=<album ID>&t=<firstTrack>&s=<shuffle>
|
||||
PlayPlaylistPath = "/transport/play-playlist" // ?id=<playlist ID>&t=<firstTrack>&s=<shuffle>
|
||||
PlayTrackPath = "/transport/play-track" // ?id=<track ID>
|
||||
PlayPausePath = "/transport/playpause"
|
||||
PausePath = "/transport/pause"
|
||||
StopPath = "/transport/stop"
|
||||
PreviousPath = "/transport/previous"
|
||||
NextPath = "/transport/next"
|
||||
TimePosPath = "/transport/timepos" // ?s=<seconds>
|
||||
SeekByPath = "/transport/seek-by" // ?s=<+/- seconds>
|
||||
VolumePath = "/volume" // ?v=<vol>
|
||||
VolumeAdjustPath = "/volume/adjust" // ?pct=<+/- percentage>
|
||||
ShowPath = "/window/show"
|
||||
QuitPath = "/window/quit"
|
||||
PingPath = "/ping"
|
||||
PlayPath = "/transport/play"
|
||||
PlayAlbumPath = "/transport/play-album" // ?id=<album ID>&t=<firstTrack>&s=<shuffle>
|
||||
PlayPlaylistPath = "/transport/play-playlist" // ?id=<playlist ID>&t=<firstTrack>&s=<shuffle>
|
||||
PlayTrackPath = "/transport/play-track" // ?id=<track ID>
|
||||
SearchAlbumPath = "/transport/search-album" // ?s=<searchQuery>
|
||||
SearchPlaylistPath = "/transport/search-playlist" // ?s=<searchQuery>
|
||||
SearchTrackPath = "/transport/search-track" // ?s=<searchQuery>
|
||||
PlayPausePath = "/transport/playpause"
|
||||
PausePath = "/transport/pause"
|
||||
StopPath = "/transport/stop"
|
||||
PreviousPath = "/transport/previous"
|
||||
NextPath = "/transport/next"
|
||||
TimePosPath = "/transport/timepos" // ?s=<seconds>
|
||||
SeekByPath = "/transport/seek-by" // ?s=<+/- seconds>
|
||||
VolumePath = "/volume" // ?v=<vol>
|
||||
VolumeAdjustPath = "/volume/adjust" // ?pct=<+/- percentage>
|
||||
ShowPath = "/window/show"
|
||||
QuitPath = "/window/quit"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Error string `json:"error"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func SetVolumePath(vol int) string {
|
||||
@@ -52,3 +60,18 @@ func BuildPlayPlaylistPath(id string, firstTrack int, shuffle bool) string {
|
||||
func BuildPlayTrackPath(id string) string {
|
||||
return fmt.Sprintf("%s?id=%s", PlayTrackPath, id)
|
||||
}
|
||||
|
||||
func BuildSearchAlbumPath(search string) string {
|
||||
s := url.QueryEscape(search)
|
||||
return fmt.Sprintf("%s?s=%s", SearchAlbumPath, s)
|
||||
}
|
||||
|
||||
func BuildSearchPlaylistPath(search string) string {
|
||||
s := url.QueryEscape(search)
|
||||
return fmt.Sprintf("%s?s=%s", SearchPlaylistPath, s)
|
||||
}
|
||||
|
||||
func BuildSearchTrackPath(search string) string {
|
||||
s := url.QueryEscape(search)
|
||||
return fmt.Sprintf("%s?s=%s", SearchTrackPath, s)
|
||||
}
|
||||
|
||||
+48
-24
@@ -30,79 +30,103 @@ func Connect() (*Client, error) {
|
||||
}
|
||||
|
||||
func (c *Client) Ping() error {
|
||||
if c.sendRequest(PingPath) != nil {
|
||||
return ErrPingFail
|
||||
}
|
||||
return nil
|
||||
_, err := c.sendRequest(PingPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Play() error {
|
||||
return c.sendRequest(PlayPath)
|
||||
_, err := c.sendRequest(PlayPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Pause() error {
|
||||
return c.sendRequest(PausePath)
|
||||
_, err := c.sendRequest(PausePath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) PlayAlbum(id string, firstTrack int, shuffle bool) error {
|
||||
return c.sendRequest(BuildPlayAlbumPath(id, firstTrack, shuffle))
|
||||
_, err := c.sendRequest(BuildPlayAlbumPath(id, firstTrack, shuffle))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) PlayPlaylist(id string, firstTrack int, shuffle bool) error {
|
||||
return c.sendRequest(BuildPlayPlaylistPath(id, firstTrack, shuffle))
|
||||
_, err := c.sendRequest(BuildPlayPlaylistPath(id, firstTrack, shuffle))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SearchAlbum(search string) (string, error) {
|
||||
return c.sendRequest(BuildSearchAlbumPath(search))
|
||||
}
|
||||
|
||||
func (c *Client) SearchPlaylist(search string) (string, error) {
|
||||
return c.sendRequest(BuildSearchPlaylistPath(search))
|
||||
}
|
||||
|
||||
func (c *Client) SearchTrack(search string) (string, error) {
|
||||
return c.sendRequest(BuildSearchTrackPath(search))
|
||||
}
|
||||
|
||||
func (c *Client) PlayTrack(id string) error {
|
||||
return c.sendRequest(BuildPlayTrackPath(id))
|
||||
_, err := c.sendRequest(BuildPlayTrackPath(id))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) PlayPause() error {
|
||||
return c.sendRequest(PlayPausePath)
|
||||
_, err := c.sendRequest(PlayPausePath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SeekNext() error {
|
||||
return c.sendRequest(NextPath)
|
||||
_, err := c.sendRequest(NextPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SeekBackOrPrevious() error {
|
||||
return c.sendRequest(PreviousPath)
|
||||
_, err := c.sendRequest(PreviousPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SeekSeconds(secs float64) error {
|
||||
return c.sendRequest(SeekToSecondsPath(secs))
|
||||
_, err := c.sendRequest(SeekToSecondsPath(secs))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SeekBySeconds(secs float64) error {
|
||||
return c.sendRequest(SeekBySecondsPath(secs))
|
||||
_, err := c.sendRequest(SeekBySecondsPath(secs))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SetVolume(vol int) error {
|
||||
return c.sendRequest(SetVolumePath(vol))
|
||||
_, err := c.sendRequest(SetVolumePath(vol))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) AdjustVolumePct(pct float64) error {
|
||||
return c.sendRequest(AdjustVolumePctPath(pct))
|
||||
_, err := c.sendRequest(AdjustVolumePctPath(pct))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Show() error {
|
||||
return c.sendRequest(ShowPath)
|
||||
_, err := c.sendRequest(ShowPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Quit() error {
|
||||
return c.sendRequest(QuitPath)
|
||||
_, err := c.sendRequest(QuitPath)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) sendRequest(path string) error {
|
||||
func (c *Client) sendRequest(path string) (string, error) {
|
||||
resp, err := c.httpC.Get("http://supersonic/" + path)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var r Response
|
||||
json.NewDecoder(resp.Body).Decode(&r)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var r Response
|
||||
json.NewDecoder(resp.Body).Decode(&r)
|
||||
return errors.New(r.Error)
|
||||
return "", errors.New(r.Error)
|
||||
}
|
||||
return nil
|
||||
return string(r.Data), nil
|
||||
}
|
||||
|
||||
+78
-2
@@ -6,6 +6,9 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
)
|
||||
|
||||
type PlaybackHandler interface {
|
||||
@@ -32,12 +35,13 @@ type IPCServer interface {
|
||||
type serverImpl struct {
|
||||
server *http.Server
|
||||
pbHandler PlaybackHandler
|
||||
mp *mediaprovider.MediaProvider
|
||||
showFn func()
|
||||
quitFn func()
|
||||
}
|
||||
|
||||
func NewServer(pbHandler PlaybackHandler, showFn, quitFn func()) IPCServer {
|
||||
s := &serverImpl{pbHandler: pbHandler, showFn: showFn, quitFn: quitFn}
|
||||
func NewServer(pbHandler PlaybackHandler, mp *mediaprovider.MediaProvider, showFn, quitFn func()) IPCServer {
|
||||
s := &serverImpl{pbHandler: pbHandler, mp: mp, showFn: showFn, quitFn: quitFn}
|
||||
s.server = &http.Server{
|
||||
Handler: s.createHandler(),
|
||||
}
|
||||
@@ -96,6 +100,52 @@ func (s *serverImpl) createHandler() http.Handler {
|
||||
s.pbHandler.PlayTrack(id)
|
||||
s.writeOK(w)
|
||||
})
|
||||
m.HandleFunc(SearchAlbumPath, s.makeSearchEndpointHandler(func(search string) (any, error) {
|
||||
filter := mediaprovider.NewAlbumFilter(mediaprovider.AlbumFilterOptions{})
|
||||
i := (*s.mp).SearchAlbums(search, filter)
|
||||
|
||||
album := i.Next()
|
||||
albums := make([]mediaprovider.Album, 0)
|
||||
for album != nil {
|
||||
albums = append(albums, *album)
|
||||
album = i.Next()
|
||||
}
|
||||
|
||||
return albums, nil
|
||||
}))
|
||||
m.HandleFunc(SearchPlaylistPath, s.makeSearchEndpointHandler(func(search string) (any, error) {
|
||||
all, err := (*s.mp).GetPlaylists()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
search = strings.ReplaceAll(search, " ", "")
|
||||
search = strings.ToLower(search)
|
||||
|
||||
filtered := make([]mediaprovider.Playlist, 0)
|
||||
for i := 0; i < len(all); i++ {
|
||||
playlist := all[i]
|
||||
name := strings.ReplaceAll(playlist.Name, " ", "")
|
||||
name = strings.ToLower(name)
|
||||
if strings.Contains(name, search) {
|
||||
filtered = append(filtered, *playlist)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}))
|
||||
m.HandleFunc(SearchTrackPath, s.makeSearchEndpointHandler(func(search string) (any, error) {
|
||||
i := (*s.mp).IterateTracks(search)
|
||||
|
||||
track := i.Next()
|
||||
tracks := make([]mediaprovider.Track, 0)
|
||||
for track != nil {
|
||||
tracks = append(tracks, *track)
|
||||
track = i.Next()
|
||||
}
|
||||
|
||||
return tracks, nil
|
||||
}))
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -137,6 +187,22 @@ func (s *serverImpl) makeTracklistEndpointHandler(f func(string, int, bool) erro
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serverImpl) makeSearchEndpointHandler(f func(string) (any, error)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
search := r.URL.Query().Get("s")
|
||||
data, err := f(search)
|
||||
if err != nil {
|
||||
s.writeErr(w, err)
|
||||
return
|
||||
}
|
||||
bytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
s.writeErr(w, err)
|
||||
}
|
||||
s.writeData(w, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serverImpl) writeOK(w http.ResponseWriter) (int, error) {
|
||||
var r Response
|
||||
b, err := json.Marshal(&r)
|
||||
@@ -146,6 +212,16 @@ func (s *serverImpl) writeOK(w http.ResponseWriter) (int, error) {
|
||||
return w.Write(b)
|
||||
}
|
||||
|
||||
func (s *serverImpl) writeData(w http.ResponseWriter, data []byte) (int, error) {
|
||||
r := Response{Data: data}
|
||||
b, err := json.Marshal(&r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
return w.Write(b)
|
||||
}
|
||||
|
||||
func (s *serverImpl) writeErr(w http.ResponseWriter, err error) (int, error) {
|
||||
r := Response{Error: err.Error()}
|
||||
b, err := json.Marshal(&r)
|
||||
|
||||
Reference in New Issue
Block a user