Skip to content

Commit

Permalink
Add command line actions via json config file
Browse files Browse the repository at this point in the history
  • Loading branch information
scosman committed Mar 21, 2023
1 parent 8833e68 commit 3d8225a
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 3 deletions.
62 changes: 62 additions & 0 deletions actions/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package actions

import (
"encoding/json"
"errors"
"io/ioutil"
"log"
"os/exec"
)

type ActionName string

type AirplayCommandLineAction struct {
DeviceName string `json:"device_name"`
Command string `json:"command"`
CommandArgs string `json:"command_args"`
ActionName ActionName `json:"action"`
}

type AirplayMusicActionRunner struct {
Actions []*AirplayCommandLineAction `json:"actions"`
}

const (
ACTION_NAME_START_PLAYING ActionName = "start_playing"
ACTION_NAME_END_PLAYING ActionName = "end_playing"
)

// func DefaultParams(service string) *QueryParam {
func NewAirplayMusicActionRunner(configFilePath string) (*AirplayMusicActionRunner, error) {
configBytes, err := ioutil.ReadFile(configFilePath)
if err != nil {
return nil, err
}
var parsedRunner AirplayMusicActionRunner
err = json.Unmarshal(configBytes, &parsedRunner)
if err != nil {
return nil, err
}

for _, action := range parsedRunner.Actions {
if action.ActionName != ACTION_NAME_START_PLAYING && action.ActionName != ACTION_NAME_END_PLAYING {
return nil, errors.New("Invalid action name")
}
}

return &parsedRunner, nil
}

func (r *AirplayMusicActionRunner) RunActionForDeviceState(deviceName string, isPlaying bool) {
for _, action := range r.Actions {
if action.DeviceName == deviceName {
if (isPlaying && action.ActionName == ACTION_NAME_START_PLAYING) || (!isPlaying && action.ActionName == ACTION_NAME_END_PLAYING) {
log.Printf("Running command: %v %v", action.Command, action.CommandArgs)
cmd := exec.Command(action.Command, action.CommandArgs)
if err := cmd.Run(); err != nil {
log.Printf("Error running command: %v %v", action.Command, action.CommandArgs)
}
}
}
}
}
30 changes: 30 additions & 0 deletions actions/actions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package actions

import (
"log"
"os"
"path/filepath"
"testing"
)

func TestActionsParseValid(t *testing.T) {
workingDirectory, _ := os.Getwd()
testFile := filepath.Join(workingDirectory, "../test_files/valid_actions_1.json")
log.Printf("wd: %v", testFile)

actions, err := NewAirplayMusicActionRunner(testFile)
if err != nil {
t.Fatal(err)
}
if len(actions.Actions) != 2 {
t.Fatal("parse error -- wrong count")
}
first := actions.Actions[0]
if first.DeviceName != "device1" || first.Command != "echo" || first.ActionName != "start_playing" {
t.Fatal("didn't parse first action")
}
second := actions.Actions[1]
if second.DeviceName != "Stereo" || second.Command != "echo" || second.ActionName != "end_playing" {
t.Fatal("didn't parse second action")
}
}
15 changes: 15 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,28 @@ package main

import (
"fmt"
"log"
"math"
"os"

"github.com/scosman/airplay-music-watcher/actions"
"github.com/scosman/airplay-music-watcher/mdns"
)

const DeviceSupportsRelayBitmask = 0x800

func main() {
args := os.Args
if len(args) != 2 {
// first arg is program path, so 2 == 1...
log.Fatal("command requires exactly 1 arg -- the path to the json config file")
}
jsonFilePath := args[1]
actionRunner, err := actions.NewAirplayMusicActionRunner(jsonFilePath)
if err != nil {
log.Fatalf("Error parsing json config file: %v", err)
}

entriesCh := make(chan *mdns.AirplayFlagsEntry, 4)
defer close(entriesCh)
go func() {
Expand All @@ -21,6 +35,7 @@ func main() {
// https://github.com/openairplay/airplay-spec/blob/master/src/status_flags.md
isPlaying := (DeviceSupportsRelayBitmask & entry.Flags) > 0
fmt.Printf("Airplay Device \"%s\" event, is playing: %t\n", entry.DeviceName, isPlaying)
actionRunner.RunActionForDeviceState(entry.DeviceName, isPlaying)
}
}()

Expand Down
3 changes: 0 additions & 3 deletions mdns/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ func (c *client) query(params *QueryParam) error {
inp.Info = strings.Join(rr.Txt, "|")
inp.InfoFields = rr.Txt
inp.hasTXT = true
//fmt.Printf("TXT Entry: %v\nExtra: %v\n", resp.Answer, resp.Extra)
for _, answer := range resp.Answer {
hostName := answer.Header().Name
if strings.Contains(hostName, "._airplay._tcp.local.") {
Expand Down Expand Up @@ -394,8 +393,6 @@ func (c *client) query(params *QueryParam) error {
m.SetQuestion(inp.Name, dns.TypePTR)
m.RecursionDesired = false

// TODO
//log.Print("skipping sending up entry")
if err := c.sendQuery(m); err != nil {
log.Printf("[ERR] mdns: Failed to query instance %s: %v", inp.Name, err)
}
Expand Down
16 changes: 16 additions & 0 deletions test_files/valid_actions_1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"actions": [
{
"device_name": "device1",
"action": "start_playing",
"command": "echo",
"command_args": "'value to echo device 1'"
},
{
"device_name": "Stereo",
"action": "end_playing",
"command": "echo",
"command_args": "'value to echo for stereo'"
}
]
}
16 changes: 16 additions & 0 deletions test_files/valid_actions_2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"actions": [
{
"device_name": "Stereo",
"action": "start_playing",
"command": "echo",
"command_args": "'value to echo stereo start'"
},
{
"device_name": "Stereo",
"action": "end_playing",
"command": "echo",
"command_args": "'value to echo for stereo end'"
}
]
}

0 comments on commit 3d8225a

Please sign in to comment.