From 2fc414efddb48a975a2305bae2672f924e1f4e83 Mon Sep 17 00:00:00 2001 From: Kevin Schoon Date: Sat, 3 Feb 2018 22:13:46 -0500 Subject: [PATCH] add socket server for scriptable status output --- main.go | 22 ++++++++++++++++ server.go | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ task.go | 9 +++++++ types.go | 9 +++++++ ui.go | 24 ++++++++--------- util.go | 12 +++++++++ 6 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 server.go diff --git a/main.go b/main.go index 402024a..2da6240 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,10 @@ func start(path *string) func(*cli.Cmd) { } runner, err := NewTaskRunner(task, db, NewXnotifier(*path+"/icon.png")) maybe(err) + server, err := NewServer(*path+"/pomo.sock", runner) + maybe(err) + server.Start() + defer server.Stop() runner.Start() startUI(runner) } @@ -94,6 +98,23 @@ func _delete(path *string) func(*cli.Cmd) { } } +func _status(path *string) func(*cli.Cmd) { + return func(cmd *cli.Cmd) { + cmd.Spec = "[OPTIONS]" + cmd.Action = func() { + client, err := NewClient(*path + "/pomo.sock") + if err != nil { + outputStatus(Status{}) + return + } + defer client.Close() + status, err := client.Status() + maybe(err) + outputStatus(*status) + } + } +} + func main() { app := cli.App("pomo", "Pomodoro CLI") app.Spec = "[OPTIONS]" @@ -105,5 +126,6 @@ func main() { app.Command("init", "initialize the sqlite database", initialize(path)) app.Command("list l", "list historical tasks", list(path)) app.Command("delete d", "delete a stored task", _delete(path)) + app.Command("status st", "output the current status", _status(path)) app.Run(os.Args) } diff --git a/server.go b/server.go new file mode 100644 index 0000000..8c6719a --- /dev/null +++ b/server.go @@ -0,0 +1,79 @@ +package main + +import ( + "encoding/json" + "net" +) + +// Server listens on a Unix domain socket +// for Pomo status requests +type Server struct { + listener net.Listener + runner *TaskRunner + running bool +} + +func (s *Server) listen() { + for s.running { + conn, err := s.listener.Accept() + if err != nil { + panic(err) + } + buf := make([]byte, 512) + // Ignore any content + conn.Read(buf) + raw, _ := json.Marshal(s.runner.Status()) + conn.Write(raw) + conn.Close() + } +} + +func (s *Server) Start() { + s.running = true + go s.listen() +} + +func (s *Server) Stop() { + s.running = false + s.listener.Close() +} + +func NewServer(path string, runner *TaskRunner) (*Server, error) { + listener, err := net.Listen("unix", path) + if err != nil { + return nil, err + } + return &Server{listener: listener, runner: runner}, nil +} + +// Client makes requests to a listening +// pomo server to check the status of +// any currently running task session. +type Client struct { + conn net.Conn +} + +func (c Client) read(statusCh chan *Status) { + buf := make([]byte, 512) + n, _ := c.conn.Read(buf) + status := &Status{} + json.Unmarshal(buf[0:n], status) + statusCh <- status +} + +func (c Client) Status() (*Status, error) { + statusCh := make(chan *Status) + c.conn.Write([]byte("status")) + go c.read(statusCh) + return <-statusCh, nil +} + +func (c Client) Close() error { return c.conn.Close() } + +func NewClient(path string) (*Client, error) { + conn, err := net.Dial("unix", path) + if err != nil { + return nil, err + } + return &Client{conn: conn}, nil +} diff --git a/task.go b/task.go index 82316de..9823484 100644 --- a/task.go +++ b/task.go @@ -118,3 +118,12 @@ func (t *TaskRunner) Toggle() { func (t *TaskRunner) Pause() { t.pause <- true } + +func (t *TaskRunner) Status() *Status { + return &Status{ + State: t.state, + Count: t.count, + NPomodoros: t.nPomodoros, + Remaining: t.TimeRemaining(), + } +} diff --git a/types.go b/types.go index b99be81..67a5dff 100644 --- a/types.go +++ b/types.go @@ -163,6 +163,15 @@ func (p Pomodoro) Duration() time.Duration { return (p.End.Sub(p.Start)) } +// Status is used to communicate the state +// of a running Pomodoro session +type Status struct { + State State `json:"state"` + Remaining time.Duration `json:"remaining"` + Count int `json:"count"` + NPomodoros int `json:"n_pomodoros"` +} + // Notifier sends a system notification type Notifier interface { Notify(string, string) error diff --git a/ui.go b/ui.go index b1243e0..d36b95b 100644 --- a/ui.go +++ b/ui.go @@ -5,9 +5,9 @@ import ( "github.com/gizak/termui" ) -func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer { +func render(wheel *Wheel, status *Status) termui.GridBufferer { var text string - switch runner.state { + switch status.State { case RUNNING: text = fmt.Sprintf( `[%d/%d] Pomodoros completed @@ -17,10 +17,10 @@ func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer { [q] - quit [p] - pause `, - runner.count, - runner.nPomodoros, + status.Count, + status.NPomodoros, wheel, - runner.TimeRemaining(), + status.Remaining, ) case BREAKING: text = `It is time to take a break! @@ -49,10 +49,10 @@ func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer { } par := termui.NewPar(text) par.Height = 8 - par.BorderLabel = fmt.Sprintf("Pomo - %s", runner.state) + par.BorderLabel = fmt.Sprintf("Pomo - %s", status.State) par.BorderLabelFg = termui.ColorWhite par.BorderFg = termui.ColorRed - if runner.state == RUNNING { + if status.State == RUNNING { par.BorderFg = termui.ColorGreen } return par @@ -94,24 +94,24 @@ func startUI(runner *TaskRunner) { defer termui.Close() - termui.Render(centered(status(&wheel, runner))) + termui.Render(centered(render(&wheel, runner.Status()))) termui.Handle("/timer/1s", func(termui.Event) { - termui.Render(centered(status(&wheel, runner))) + termui.Render(centered(render(&wheel, runner.Status()))) }) termui.Handle("/sys/wnd/resize", func(termui.Event) { - termui.Render(centered(status(&wheel, runner))) + termui.Render(centered(render(&wheel, runner.Status()))) }) termui.Handle("/sys/kbd/", func(termui.Event) { runner.Toggle() - termui.Render(centered(status(&wheel, runner))) + termui.Render(centered(render(&wheel, runner.Status()))) }) termui.Handle("/sys/kbd/p", func(termui.Event) { runner.Pause() - termui.Render(centered(status(&wheel, runner))) + termui.Render(centered(render(&wheel, runner.Status()))) }) termui.Handle("/sys/kbd/q", func(termui.Event) { diff --git a/util.go b/util.go index a2de6ec..f7a5680 100644 --- a/util.go +++ b/util.go @@ -74,3 +74,15 @@ func summerizeTasks(config *Config, tasks []*Task) { fmt.Printf("\n") } } + +func outputStatus(status Status) { + state := "?" + if status.State >= RUNNING { + state = string(status.State.String()[0]) + } + if status.State == RUNNING { + fmt.Printf("%s [%d/%d] %s", state, status.Count, status.NPomodoros, status.Remaining) + } else { + fmt.Printf("%s [%d/%d] -", state, status.Count, status.NPomodoros) + } +}