Commit
+143 -12 +/-6 browse
1 | diff --git a/main.go b/main.go |
2 | index 402024a..2da6240 100644 |
3 | --- a/main.go |
4 | +++ b/main.go |
5 | @@ -32,6 +32,10 @@ func start(path *string) func(*cli.Cmd) { |
6 | } |
7 | runner, err := NewTaskRunner(task, db, NewXnotifier(*path+"/icon.png")) |
8 | maybe(err) |
9 | + server, err := NewServer(*path+"/pomo.sock", runner) |
10 | + maybe(err) |
11 | + server.Start() |
12 | + defer server.Stop() |
13 | runner.Start() |
14 | startUI(runner) |
15 | } |
16 | @@ -94,6 +98,23 @@ func _delete(path *string) func(*cli.Cmd) { |
17 | } |
18 | } |
19 | |
20 | + func _status(path *string) func(*cli.Cmd) { |
21 | + return func(cmd *cli.Cmd) { |
22 | + cmd.Spec = "[OPTIONS]" |
23 | + cmd.Action = func() { |
24 | + client, err := NewClient(*path + "/pomo.sock") |
25 | + if err != nil { |
26 | + outputStatus(Status{}) |
27 | + return |
28 | + } |
29 | + defer client.Close() |
30 | + status, err := client.Status() |
31 | + maybe(err) |
32 | + outputStatus(*status) |
33 | + } |
34 | + } |
35 | + } |
36 | + |
37 | func main() { |
38 | app := cli.App("pomo", "Pomodoro CLI") |
39 | app.Spec = "[OPTIONS]" |
40 | @@ -105,5 +126,6 @@ func main() { |
41 | app.Command("init", "initialize the sqlite database", initialize(path)) |
42 | app.Command("list l", "list historical tasks", list(path)) |
43 | app.Command("delete d", "delete a stored task", _delete(path)) |
44 | + app.Command("status st", "output the current status", _status(path)) |
45 | app.Run(os.Args) |
46 | } |
47 | diff --git a/server.go b/server.go |
48 | new file mode 100644 |
49 | index 0000000..8c6719a |
50 | --- /dev/null |
51 | +++ b/server.go |
52 | @@ -0,0 +1,79 @@ |
53 | + package main |
54 | + |
55 | + import ( |
56 | + "encoding/json" |
57 | + "net" |
58 | + ) |
59 | + |
60 | + // Server listens on a Unix domain socket |
61 | + // for Pomo status requests |
62 | + type Server struct { |
63 | + listener net.Listener |
64 | + runner *TaskRunner |
65 | + running bool |
66 | + } |
67 | + |
68 | + func (s *Server) listen() { |
69 | + for s.running { |
70 | + conn, err := s.listener.Accept() |
71 | + if err != nil { |
72 | + panic(err) |
73 | + } |
74 | + buf := make([]byte, 512) |
75 | + // Ignore any content |
76 | + conn.Read(buf) |
77 | + raw, _ := json.Marshal(s.runner.Status()) |
78 | + conn.Write(raw) |
79 | + conn.Close() |
80 | + } |
81 | + } |
82 | + |
83 | + func (s *Server) Start() { |
84 | + s.running = true |
85 | + go s.listen() |
86 | + } |
87 | + |
88 | + func (s *Server) Stop() { |
89 | + s.running = false |
90 | + s.listener.Close() |
91 | + } |
92 | + |
93 | + func NewServer(path string, runner *TaskRunner) (*Server, error) { |
94 | + listener, err := net.Listen("unix", path) |
95 | + if err != nil { |
96 | + return nil, err |
97 | + } |
98 | + return &Server{listener: listener, runner: runner}, nil |
99 | + } |
100 | + |
101 | + // Client makes requests to a listening |
102 | + // pomo server to check the status of |
103 | + // any currently running task session. |
104 | + type Client struct { |
105 | + conn net.Conn |
106 | + } |
107 | + |
108 | + func (c Client) read(statusCh chan *Status) { |
109 | + buf := make([]byte, 512) |
110 | + n, _ := c.conn.Read(buf) |
111 | + status := &Status{} |
112 | + json.Unmarshal(buf[0:n], status) |
113 | + statusCh <- status |
114 | + } |
115 | + |
116 | + func (c Client) Status() (*Status, error) { |
117 | + statusCh := make(chan *Status) |
118 | + c.conn.Write([]byte("status")) |
119 | + go c.read(statusCh) |
120 | + return <-statusCh, nil |
121 | + } |
122 | + |
123 | + func (c Client) Close() error { return c.conn.Close() } |
124 | + |
125 | + func NewClient(path string) (*Client, error) { |
126 | + conn, err := net.Dial("unix", path) |
127 | + if err != nil { |
128 | + return nil, err |
129 | + } |
130 | + return &Client{conn: conn}, nil |
131 | + } |
132 | diff --git a/task.go b/task.go |
133 | index 82316de..9823484 100644 |
134 | --- a/task.go |
135 | +++ b/task.go |
136 | @@ -118,3 +118,12 @@ func (t *TaskRunner) Toggle() { |
137 | func (t *TaskRunner) Pause() { |
138 | t.pause <- true |
139 | } |
140 | + |
141 | + func (t *TaskRunner) Status() *Status { |
142 | + return &Status{ |
143 | + State: t.state, |
144 | + Count: t.count, |
145 | + NPomodoros: t.nPomodoros, |
146 | + Remaining: t.TimeRemaining(), |
147 | + } |
148 | + } |
149 | diff --git a/types.go b/types.go |
150 | index b99be81..67a5dff 100644 |
151 | --- a/types.go |
152 | +++ b/types.go |
153 | @@ -163,6 +163,15 @@ func (p Pomodoro) Duration() time.Duration { |
154 | return (p.End.Sub(p.Start)) |
155 | } |
156 | |
157 | + // Status is used to communicate the state |
158 | + // of a running Pomodoro session |
159 | + type Status struct { |
160 | + State State `json:"state"` |
161 | + Remaining time.Duration `json:"remaining"` |
162 | + Count int `json:"count"` |
163 | + NPomodoros int `json:"n_pomodoros"` |
164 | + } |
165 | + |
166 | // Notifier sends a system notification |
167 | type Notifier interface { |
168 | Notify(string, string) error |
169 | diff --git a/ui.go b/ui.go |
170 | index b1243e0..d36b95b 100644 |
171 | --- a/ui.go |
172 | +++ b/ui.go |
173 | @@ -5,9 +5,9 @@ import ( |
174 | "github.com/gizak/termui" |
175 | ) |
176 | |
177 | - func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer { |
178 | + func render(wheel *Wheel, status *Status) termui.GridBufferer { |
179 | var text string |
180 | - switch runner.state { |
181 | + switch status.State { |
182 | case RUNNING: |
183 | text = fmt.Sprintf( |
184 | `[%d/%d] Pomodoros completed |
185 | @@ -17,10 +17,10 @@ func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer { |
186 | |
187 | [q] - quit [p] - pause |
188 | `, |
189 | - runner.count, |
190 | - runner.nPomodoros, |
191 | + status.Count, |
192 | + status.NPomodoros, |
193 | wheel, |
194 | - runner.TimeRemaining(), |
195 | + status.Remaining, |
196 | ) |
197 | case BREAKING: |
198 | text = `It is time to take a break! |
199 | @@ -49,10 +49,10 @@ func status(wheel *Wheel, runner *TaskRunner) termui.GridBufferer { |
200 | } |
201 | par := termui.NewPar(text) |
202 | par.Height = 8 |
203 | - par.BorderLabel = fmt.Sprintf("Pomo - %s", runner.state) |
204 | + par.BorderLabel = fmt.Sprintf("Pomo - %s", status.State) |
205 | par.BorderLabelFg = termui.ColorWhite |
206 | par.BorderFg = termui.ColorRed |
207 | - if runner.state == RUNNING { |
208 | + if status.State == RUNNING { |
209 | par.BorderFg = termui.ColorGreen |
210 | } |
211 | return par |
212 | @@ -94,24 +94,24 @@ func startUI(runner *TaskRunner) { |
213 | |
214 | defer termui.Close() |
215 | |
216 | - termui.Render(centered(status(&wheel, runner))) |
217 | + termui.Render(centered(render(&wheel, runner.Status()))) |
218 | |
219 | termui.Handle("/timer/1s", func(termui.Event) { |
220 | - termui.Render(centered(status(&wheel, runner))) |
221 | + termui.Render(centered(render(&wheel, runner.Status()))) |
222 | }) |
223 | |
224 | termui.Handle("/sys/wnd/resize", func(termui.Event) { |
225 | - termui.Render(centered(status(&wheel, runner))) |
226 | + termui.Render(centered(render(&wheel, runner.Status()))) |
227 | }) |
228 | |
229 | termui.Handle("/sys/kbd/<enter>", func(termui.Event) { |
230 | runner.Toggle() |
231 | - termui.Render(centered(status(&wheel, runner))) |
232 | + termui.Render(centered(render(&wheel, runner.Status()))) |
233 | }) |
234 | |
235 | termui.Handle("/sys/kbd/p", func(termui.Event) { |
236 | runner.Pause() |
237 | - termui.Render(centered(status(&wheel, runner))) |
238 | + termui.Render(centered(render(&wheel, runner.Status()))) |
239 | }) |
240 | |
241 | termui.Handle("/sys/kbd/q", func(termui.Event) { |
242 | diff --git a/util.go b/util.go |
243 | index a2de6ec..f7a5680 100644 |
244 | --- a/util.go |
245 | +++ b/util.go |
246 | @@ -74,3 +74,15 @@ func summerizeTasks(config *Config, tasks []*Task) { |
247 | fmt.Printf("\n") |
248 | } |
249 | } |
250 | + |
251 | + func outputStatus(status Status) { |
252 | + state := "?" |
253 | + if status.State >= RUNNING { |
254 | + state = string(status.State.String()[0]) |
255 | + } |
256 | + if status.State == RUNNING { |
257 | + fmt.Printf("%s [%d/%d] %s", state, status.Count, status.NPomodoros, status.Remaining) |
258 | + } else { |
259 | + fmt.Printf("%s [%d/%d] -", state, status.Count, status.NPomodoros) |
260 | + } |
261 | + } |