diff --git a/audioslave.go b/audioslave.go index 5300a73..50368e9 100644 --- a/audioslave.go +++ b/audioslave.go @@ -2,30 +2,47 @@ package audioslave import ( "context" + "encoding/json" "fmt" "github.com/bevzzz/audioslave/internal/keyboard" "github.com/bevzzz/audioslave/internal/volume" + "github.com/bevzzz/audioslave/pkg/algorithms" "github.com/bevzzz/audioslave/pkg/config" "log" ) type AudioSlave struct { - KeystrokeCounter keyboard.KeystrokeCounter - VolumeController volume.VolumeController - Config config.Application + KeystrokeCounter keyboard.KeystrokeCounter + VolumeController volume.VolumeController + Config *config.Application + ReloadConfigChannel chan bool + PauseApplicationChannel chan bool } // Start - starts the audioslave -func (s AudioSlave) Start(ctx context.Context) error { +func (s *AudioSlave) Start(ctx context.Context) error { + s.HandleConfig() + s.PauseApplicationChannel = make(chan bool) + s.ReloadConfigChannel = make(chan bool) countStrokes := s.KeystrokeCounter.Count(keyboard.NewDefaultTicker(s.Config.Config.Interval)) output := volume.NewOutput(s.Config.Config.Window, s.Config.Config.Interval, s.Config.Config.AverageCpm, s.VolumeController, s.Config.Config.MinVolume) - s.HandleConfig() for { + pause := false select { case <-ctx.Done(): return nil + case pause = <-s.PauseApplicationChannel: + // pause application + case <-s.ReloadConfigChannel: + // reload config + s.HandleConfig() + countStrokes = s.KeystrokeCounter.Count(keyboard.NewDefaultTicker(s.Config.Config.Interval)) + output = volume.NewOutput(s.Config.Config.Window, s.Config.Config.Interval, + s.Config.Config.AverageCpm, s.VolumeController, s.Config.Config.MinVolume) default: + } + if !pause { n, ok := <-countStrokes if !ok { output.Reset() @@ -37,7 +54,7 @@ func (s AudioSlave) Start(ctx context.Context) error { } // HandleConfig - handles the reading and saving of the config -func (s AudioSlave) HandleConfig() { +func (s *AudioSlave) HandleConfig() { err := s.Config.Read() if err != nil && s.Config.Config.Verbose { log.Println("No config found") @@ -57,7 +74,40 @@ func (s AudioSlave) HandleConfig() { } // Stop - stops the audioslave -func (s AudioSlave) Stop() { +func (s *AudioSlave) Stop() { s.KeystrokeCounter.Stop() } + +// ChangeAlg - changes algorithm +func (s *AudioSlave) ChangeAlg(name string, data any, increase bool, reduce bool) error { + newAlgo := algorithms.AlgorithmByName(name) + dataRaw, err := json.Marshal(&data) + if err != nil { + return err + } + err = json.Unmarshal(dataRaw, &newAlgo) + if err != nil { + return err + } + if increase { + s.Config.IncreaseAlg = newAlgo + } + if reduce { + s.Config.ReduceAlg = newAlgo + } + s.Config.Write() + return nil +} + +func (s *AudioSlave) Pause() { + s.PauseApplicationChannel <- true +} + +func (s *AudioSlave) Resume() { + s.PauseApplicationChannel <- false +} + +func (s *AudioSlave) ReloadConfig() { + s.ReloadConfigChannel <- true +} diff --git a/cmd/audioslave/main.go b/cmd/audioslave/main.go index 47ea26e..5fd588c 100644 --- a/cmd/audioslave/main.go +++ b/cmd/audioslave/main.go @@ -8,6 +8,7 @@ import ( "github.com/bevzzz/audioslave/internal/util" "github.com/bevzzz/audioslave/internal/volume" "github.com/bevzzz/audioslave/pkg/algorithms" + "github.com/bevzzz/audioslave/pkg/api/websocket" "github.com/bevzzz/audioslave/pkg/config" "log" "os" @@ -22,10 +23,10 @@ func main() { } ctx, cancel := context.WithCancel(context.Background()) - as := audioslave.AudioSlave{ + as := &audioslave.AudioSlave{ KeystrokeCounter: keyboard.NewKeystrokeCounter(), VolumeController: &volume.ItchynyVolumeController{}, - Config: config.Application{ + Config: &config.Application{ Config: *conf, // Default algs ReduceAlg: &algorithms.Linear{ @@ -50,7 +51,19 @@ func main() { os.Exit(0) }() log.Println("Starting application...") - err := as.Start(ctx) + w := websocket.Websocket{ + Application: as, + Port: "10001", + } + var err error + switch conf.Mode { + case "cli": + err = as.Start(ctx) + case "websocket": + err = w.Start(ctx) + default: + log.Fatalf("mode %s not found", conf.Mode) + } if err != nil { log.Fatal(err) } diff --git a/go.mod b/go.mod index 0418826..7346e23 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d + github.com/gorilla/websocket v1.5.0 github.com/itchyny/volume-go v0.2.1 ) diff --git a/go.sum b/go.sum index 48a7be7..838813f 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,10 @@ -github.com/MarinX/keylogger v0.0.0-20210528193429-a54d7834cc1a h1:ItKXWegGGThcahUf+ylKFa5pwqkRJofaOyeGdzwO2mM= -github.com/MarinX/keylogger v0.0.0-20210528193429-a54d7834cc1a/go.mod h1:aKzZ7D15UvH5LboXkeLmcNi+s/f805vUfB+BfW1fqd4= github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d h1:2xp1BQbqcDDaikHnASWpVZRjibOxu7y9LhAv04whugI= github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I= -github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= -github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/itchyny/volume-go v0.2.1 h1:NiVdnIp3dyCBnygQoBLV9ecAk7Vk4KHfiZFJGvCCIm0= github.com/itchyny/volume-go v0.2.1/go.mod h1:YdvjyTIcPXyGcckaIHTfga+ItdhGZQoWhzOORajlkkE= github.com/moutend/go-wca v0.2.0 h1:AEzY6ltC5zPCldKyMYdyXv3TaLqwxSW1TIradqNqRpU= diff --git a/internal/util/Util.go b/internal/util/Util.go index 8967a60..6d5db8d 100644 --- a/internal/util/Util.go +++ b/internal/util/Util.go @@ -2,7 +2,7 @@ package util import "encoding/json" -func PrettyPrint(i interface{}) string { +func PrettyPrint(i any) string { s, _ := json.MarshalIndent(i, "", "\t") return string(s) } diff --git a/pkg/api/websocket/Commands.go b/pkg/api/websocket/Commands.go new file mode 100644 index 0000000..a063cc2 --- /dev/null +++ b/pkg/api/websocket/Commands.go @@ -0,0 +1,51 @@ +package websocket + +import "fmt" + +type CommandType string + +var ( + ACKCommand CommandType = "ACK" + ERRCommand CommandType = "ERR" + ALGCommand CommandType = "ALG" + STOPCommand CommandType = "STOP" + PAUSECommand CommandType = "PAUSE" + RESUMECommand CommandType = "RESUME" + RELOADCONFIGCommand CommandType = "RELOADCONFIG" +) + +var ( + ACK = Command{ + Type: ACKCommand, + Payload: nil, + } + ERR = Command{ + Type: ERRCommand, + Payload: nil, + } +) + +type Command struct { + Type CommandType + Payload any +} + +type commandAlg struct { + Type string + Reduce bool + Increase bool + Data any +} + +// Decode - decodes the command +func (c *Command) Decode() error { + switch c.Type { + case ALGCommand: + _, ok := c.Payload.(commandAlg) + if !ok { + return fmt.Errorf("could not convert") + } + default: + } + return nil +} diff --git a/pkg/api/websocket/Websocket.go b/pkg/api/websocket/Websocket.go new file mode 100644 index 0000000..3f0c6da --- /dev/null +++ b/pkg/api/websocket/Websocket.go @@ -0,0 +1,122 @@ +package websocket + +import ( + "context" + "fmt" + "github.com/bevzzz/audioslave" + "github.com/gorilla/websocket" + "log" + "net/http" +) + +type Websocket struct { + Application *audioslave.AudioSlave + Port string + Upgrader websocket.Upgrader +} + +// Start - starts the websocket API and the application +func (w *Websocket) Start(ctx context.Context) error { + w.Upgrader = websocket.Upgrader{} // use default options + ctx, cancel := context.WithCancel(ctx) + go func() { + server := &http.Server{Addr: fmt.Sprintf("localhost:%s", w.Port), + Handler: w.socketHandler(ctx, cancel)} + err := server.ListenAndServe() + if err != nil { + log.Println(err) + w.Application.Stop() + } + }() + err := w.Application.Start(ctx) + if err != nil { + return err + } + return nil +} + +// socketHandler - handler func for incoming request +func (w *Websocket) socketHandler(ctx context.Context, cancel context.CancelFunc) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + conn, err := w.Upgrader.Upgrade(writer, request, nil) + if err != nil { + return + } + defer func() { + err := conn.Close() + log.Println(err) + cancel() + }() + for { + select { + case <-ctx.Done(): + return + default: + } + command := &Command{} + // read json + err := conn.ReadJSON(command) + if err != nil { + log.Println(err) + conn.WriteJSON(&ERR) + continue + } + // write ack + err = conn.WriteJSON(&ACK) + if err != nil { + log.Println(err) + conn.WriteJSON(&ERR) + return + } + // decode command + err = command.Decode() + if err != nil { + log.Println(err) + conn.WriteJSON(&ERR) + continue + } + // process command + resp, err := w.ProcessCommand(*command, cancel) + if err != nil { + log.Println(err) + conn.WriteJSON(&ERR) + continue + } + // write resp. ack or data structure + err = conn.WriteJSON(&resp) + if err != nil { + log.Println(err) + conn.WriteJSON(&ERR) + return + } + } + } +} + +// ProcessCommand - Process a command and returns a response +func (w *Websocket) ProcessCommand(command Command, cancel context.CancelFunc) (any, error) { + switch command.Type { + case STOPCommand: + w.Stop() + cancel() + case ALGCommand: + payload := command.Payload.(commandAlg) + err := w.Application.ChangeAlg(payload.Type, payload.Data, payload.Increase, payload.Reduce) + if err != nil { + return nil, err + } + case PAUSECommand: + w.Application.Pause() + case RESUMECommand: + w.Application.Resume() + case RELOADCONFIGCommand: + w.Application.ReloadConfig() + default: + } + return &ACK, nil +} + +// Stop - stops the application underneath +func (w *Websocket) Stop() { + w.Application.Stop() +} diff --git a/pkg/config/Flags.go b/pkg/config/Flags.go index 29c24a8..e896adc 100644 --- a/pkg/config/Flags.go +++ b/pkg/config/Flags.go @@ -14,6 +14,7 @@ type Config struct { MaxVolume int AverageCpm int Interval, Window time.Duration + Mode string Verbose bool } @@ -27,6 +28,7 @@ func ParseCommand() *Config { Interval: time.Second, Window: 10 * time.Second, Verbose: false, + Mode: "websocket", } minVolume := flag.Int("min-volume", config.MinVolume, "set the minimum volume") @@ -36,6 +38,7 @@ func ParseCommand() *Config { window := flag.Duration("window", config.Window, "change output based on last N values") path := flag.String("path", config.Path, "config path to safe and load") verbose := flag.Bool("verbose", config.Verbose, "verbose logs") + mode := flag.String("mode", config.Mode, "application mode") flag.Parse() @@ -46,6 +49,7 @@ func ParseCommand() *Config { config.Interval = *interval config.Window = *window config.Verbose = *verbose + config.Mode = *mode return config }