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")) runner, err := NewTaskRunner(task, db, NewXnotifier(*path+"/icon.png"))
maybe(err) maybe(err)
server, err := NewServer(*path+"/pomo.sock", runner)
maybe(err)
server.Start()
defer server.Stop()
runner.Start() runner.Start()
startUI(runner) 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() { func main() {
app := cli.App("pomo", "Pomodoro CLI") app := cli.App("pomo", "Pomodoro CLI")
app.Spec = "[OPTIONS]" app.Spec = "[OPTIONS]"
@ -105,5 +126,6 @@ func main() {
app.Command("init", "initialize the sqlite database", initialize(path)) app.Command("init", "initialize the sqlite database", initialize(path))
app.Command("list l", "list historical tasks", list(path)) app.Command("list l", "list historical tasks", list(path))
app.Command("delete d", "delete a stored task", _delete(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) 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() { func (t *TaskRunner) Pause() {
t.pause <- true 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)) 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 // Notifier sends a system notification
type Notifier interface { type Notifier interface {
Notify(string, string) error Notify(string, string) error

24
ui.go
View File

@ -5,9 +5,9 @@ import (
"github.com/gizak/termui" "github.com/gizak/termui"
) )
func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer { func render(wheel *Wheel, status *Status) termui.GridBufferer {
var text string var text string
switch runner.state { switch status.State {
case RUNNING: case RUNNING:
text = fmt.Sprintf( text = fmt.Sprintf(
`[%d/%d] Pomodoros completed `[%d/%d] Pomodoros completed
@ -17,10 +17,10 @@ func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer {
[q] - quit [p] - pause [q] - quit [p] - pause
`, `,
runner.count, status.Count,
runner.nPomodoros, status.NPomodoros,
wheel, wheel,
runner.TimeRemaining(), status.Remaining,
) )
case BREAKING: case BREAKING:
text = `It is time to take a break! 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 := termui.NewPar(text)
par.Height = 8 par.Height = 8
par.BorderLabel = fmt.Sprintf("Pomo - %s", runner.state) par.BorderLabel = fmt.Sprintf("Pomo - %s", status.State)
par.BorderLabelFg = termui.ColorWhite par.BorderLabelFg = termui.ColorWhite
par.BorderFg = termui.ColorRed par.BorderFg = termui.ColorRed
if runner.state == RUNNING { if status.State == RUNNING {
par.BorderFg = termui.ColorGreen par.BorderFg = termui.ColorGreen
} }
return par return par
@ -94,24 +94,24 @@ func startUI(runner *TaskRunner) {
defer termui.Close() defer termui.Close()
termui.Render(centered(status(&wheel, runner))) termui.Render(centered(render(&wheel, runner.Status())))
termui.Handle("/timer/1s", func(termui.Event) { 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.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) { termui.Handle("/sys/kbd/<enter>", func(termui.Event) {
runner.Toggle() runner.Toggle()
termui.Render(centered(status(&wheel, runner))) termui.Render(centered(render(&wheel, runner.Status())))
}) })
termui.Handle("/sys/kbd/p", func(termui.Event) { termui.Handle("/sys/kbd/p", func(termui.Event) {
runner.Pause() runner.Pause()
termui.Render(centered(status(&wheel, runner))) termui.Render(centered(render(&wheel, runner.Status())))
}) })
termui.Handle("/sys/kbd/q", func(termui.Event) { 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") 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)
}
}