1 | package main
|
2 |
|
3 | import (
|
4 | "database/sql"
|
5 | "time"
|
6 | )
|
7 |
|
8 | type TaskRunner struct {
|
9 | count int
|
10 | taskID int
|
11 | taskMessage string
|
12 | nPomodoros int
|
13 | origDuration time.Duration
|
14 | state State
|
15 | store *Store
|
16 | started time.Time
|
17 | pause chan bool
|
18 | toggle chan bool
|
19 | notifier Notifier
|
20 | duration time.Duration
|
21 | }
|
22 |
|
23 | func NewTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunner, error) {
|
24 | tr := &TaskRunner{
|
25 | taskID: task.ID,
|
26 | taskMessage: task.Message,
|
27 | nPomodoros: task.NPomodoros,
|
28 | origDuration: task.Duration,
|
29 | store: store,
|
30 | state: State(0),
|
31 | pause: make(chan bool),
|
32 | toggle: make(chan bool),
|
33 | notifier: notifier,
|
34 | duration: task.Duration,
|
35 | }
|
36 | return tr, nil
|
37 | }
|
38 |
|
39 | func (t *TaskRunner) Start() {
|
40 | go t.run()
|
41 | }
|
42 |
|
43 | func (t *TaskRunner) TimeRemaining() time.Duration {
|
44 | return (t.duration - time.Since(t.started)).Truncate(time.Second)
|
45 | }
|
46 |
|
47 | func (t *TaskRunner) run() error {
|
48 | for t.count < t.nPomodoros {
|
49 | // Create a new pomodoro where we
|
50 | // track the start / end time of
|
51 | // of this session.
|
52 | pomodoro := &Pomodoro{}
|
53 | // Start this pomodoro
|
54 | pomodoro.Start = time.Now()
|
55 | // Set state to RUNNING
|
56 | t.state = RUNNING
|
57 | // Create a new timer
|
58 | timer := time.NewTimer(t.duration)
|
59 | // Record our started time
|
60 | t.started = pomodoro.Start
|
61 | loop:
|
62 | select {
|
63 | case <-timer.C:
|
64 | t.state = BREAKING
|
65 | t.count++
|
66 | case <-t.toggle:
|
67 | // Catch any toggles when we
|
68 | // are not expecting them
|
69 | goto loop
|
70 | case <-t.pause:
|
71 | timer.Stop()
|
72 | // Record the remaining time of the current pomodoro
|
73 | remaining := t.TimeRemaining()
|
74 | // Change state to PAUSED
|
75 | t.state = PAUSED
|
76 | // Wait for the user to press [p]
|
77 | <-t.pause
|
78 | // Resume the timer with previous
|
79 | // remaining time
|
80 | timer.Reset(remaining)
|
81 | // Change duration
|
82 | t.started = time.Now()
|
83 | t.duration = remaining
|
84 | // Restore state to RUNNING
|
85 | t.state = RUNNING
|
86 | goto loop
|
87 | }
|
88 | pomodoro.End = time.Now()
|
89 | err := t.store.With(func(tx *sql.Tx) error {
|
90 | return t.store.CreatePomodoro(tx, t.taskID, *pomodoro)
|
91 | })
|
92 | if err != nil {
|
93 | return err
|
94 | }
|
95 | // All pomodoros completed
|
96 | if t.count == t.nPomodoros {
|
97 | break
|
98 | }
|
99 |
|
100 | t.notifier.Notify("Pomo", "It is time to take a break!")
|
101 | // Reset the duration incase it
|
102 | // was paused.
|
103 | t.duration = t.origDuration
|
104 | // User concludes the break
|
105 | <-t.toggle
|
106 |
|
107 | }
|
108 | t.notifier.Notify("Pomo", "Pomo session has completed!")
|
109 | t.state = COMPLETE
|
110 | return nil
|
111 | }
|
112 |
|
113 | func (t *TaskRunner) Toggle() {
|
114 | t.toggle <- true
|
115 | }
|
116 |
|
117 | func (t *TaskRunner) Pause() {
|
118 | t.pause <- true
|
119 | }
|
120 |
|
121 | func (t *TaskRunner) Status() *Status {
|
122 | return &Status{
|
123 | State: t.state,
|
124 | Count: t.count,
|
125 | NPomodoros: t.nPomodoros,
|
126 | Remaining: t.TimeRemaining(),
|
127 | }
|
128 | }
|