add socket server for scriptable status output

This commit is contained in:
Kevin Schoon 2018-02-03 22:13:46 -05:00
parent 6c541894ab
commit 2fc414efdd
6 changed files with 143 additions and 12 deletions

22
main.go
View File

@ -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)
}

79
server.go Normal file
View File

@ -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
}

View File

@ -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(),
}
}

View File

@ -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

24
ui.go
View File

@ -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/<enter>", 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) {

12
util.go
View File

@ -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)
}
}