2020-09-08 18:35:47 +02:00
|
|
|
package pomo
|
2018-01-20 13:03:23 +01:00
|
|
|
|
|
|
|
import (
|
2019-01-25 06:04:41 +01:00
|
|
|
"database/sql"
|
2022-05-31 08:20:58 +02:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2022-05-30 21:59:03 +02:00
|
|
|
"sync"
|
2018-01-20 13:03:23 +01:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2018-01-21 11:35:03 +01:00
|
|
|
type TaskRunner struct {
|
2018-01-27 16:58:56 +01:00
|
|
|
count int
|
|
|
|
taskID int
|
|
|
|
taskMessage string
|
|
|
|
nPomodoros int
|
|
|
|
origDuration time.Duration
|
|
|
|
state State
|
|
|
|
store *Store
|
|
|
|
started time.Time
|
2021-08-31 00:31:35 +02:00
|
|
|
stopped time.Time
|
2018-01-27 16:58:56 +01:00
|
|
|
pause chan bool
|
|
|
|
toggle chan bool
|
|
|
|
notifier Notifier
|
|
|
|
duration time.Duration
|
2022-05-30 21:59:03 +02:00
|
|
|
mu sync.Mutex
|
2022-05-31 08:20:58 +02:00
|
|
|
onEvent []string
|
2018-01-21 11:35:03 +01:00
|
|
|
}
|
|
|
|
|
2020-09-06 05:49:42 +02:00
|
|
|
func NewMockedTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunner, error) {
|
|
|
|
tr := &TaskRunner{
|
|
|
|
taskID: task.ID,
|
|
|
|
taskMessage: task.Message,
|
|
|
|
nPomodoros: task.NPomodoros,
|
|
|
|
origDuration: task.Duration,
|
|
|
|
store: store,
|
2022-05-30 21:59:03 +02:00
|
|
|
state: CREATED,
|
2020-09-06 05:49:42 +02:00
|
|
|
pause: make(chan bool),
|
|
|
|
toggle: make(chan bool),
|
|
|
|
notifier: notifier,
|
|
|
|
duration: task.Duration,
|
|
|
|
}
|
|
|
|
return tr, nil
|
|
|
|
}
|
2019-07-05 01:12:58 +02:00
|
|
|
func NewTaskRunner(task *Task, config *Config) (*TaskRunner, error) {
|
|
|
|
store, err := NewStore(config.DBPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-01-21 11:35:03 +01:00
|
|
|
tr := &TaskRunner{
|
2022-01-05 17:36:12 +01:00
|
|
|
count: len(task.Pomodoros),
|
2019-01-25 06:04:41 +01:00
|
|
|
taskID: task.ID,
|
2018-01-27 16:58:56 +01:00
|
|
|
taskMessage: task.Message,
|
|
|
|
nPomodoros: task.NPomodoros,
|
|
|
|
origDuration: task.Duration,
|
|
|
|
store: store,
|
|
|
|
state: State(0),
|
|
|
|
pause: make(chan bool),
|
|
|
|
toggle: make(chan bool),
|
2019-07-05 01:12:58 +02:00
|
|
|
notifier: NewXnotifier(config.IconPath),
|
2018-01-27 16:58:56 +01:00
|
|
|
duration: task.Duration,
|
2022-05-31 08:20:58 +02:00
|
|
|
onEvent: config.OnEvent,
|
2018-01-21 11:35:03 +01:00
|
|
|
}
|
|
|
|
return tr, nil
|
|
|
|
}
|
|
|
|
|
2018-01-26 16:07:38 +01:00
|
|
|
func (t *TaskRunner) Start() {
|
|
|
|
go t.run()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TaskRunner) TimeRemaining() time.Duration {
|
|
|
|
return (t.duration - time.Since(t.started)).Truncate(time.Second)
|
|
|
|
}
|
|
|
|
|
2021-08-31 00:31:35 +02:00
|
|
|
func (t *TaskRunner) TimePauseDuration() time.Duration {
|
|
|
|
return (time.Since(t.stopped)).Truncate(time.Second)
|
|
|
|
}
|
|
|
|
|
2019-07-05 01:12:58 +02:00
|
|
|
func (t *TaskRunner) SetState(state State) {
|
|
|
|
t.state = state
|
2022-05-31 22:35:15 +02:00
|
|
|
// execute onEvent command if variable is set
|
|
|
|
if t.onEvent != nil {
|
|
|
|
t.runOnEvent()
|
|
|
|
}
|
2019-07-05 01:12:58 +02:00
|
|
|
}
|
|
|
|
|
2022-05-31 08:20:58 +02:00
|
|
|
// execute script command specified by `onEvent` on state change
|
2022-05-31 22:35:15 +02:00
|
|
|
func (t *TaskRunner) runOnEvent() error {
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
// parse command arguments
|
|
|
|
numArgs := len(t.onEvent)
|
|
|
|
app := t.onEvent[0]
|
|
|
|
if numArgs > 1 {
|
|
|
|
args := t.onEvent[1:(numArgs + 1)]
|
|
|
|
cmd = exec.Command(app, args...)
|
|
|
|
} else {
|
|
|
|
cmd = exec.Command(app)
|
|
|
|
}
|
|
|
|
// set state in command environment
|
2022-05-31 08:20:58 +02:00
|
|
|
cmd.Env = append(os.Environ(),
|
|
|
|
fmt.Sprintf("POMO_STATE=%s", t.state),
|
|
|
|
)
|
2022-05-31 22:35:15 +02:00
|
|
|
// run command
|
2022-05-31 08:20:58 +02:00
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-26 16:07:38 +01:00
|
|
|
func (t *TaskRunner) run() error {
|
2018-01-27 16:58:56 +01:00
|
|
|
for t.count < t.nPomodoros {
|
2018-01-26 16:07:38 +01:00
|
|
|
// Create a new pomodoro where we
|
|
|
|
// track the start / end time of
|
|
|
|
// of this session.
|
|
|
|
pomodoro := &Pomodoro{}
|
|
|
|
// Start this pomodoro
|
|
|
|
pomodoro.Start = time.Now()
|
2019-07-05 01:12:58 +02:00
|
|
|
// Set state to RUNNIN
|
|
|
|
t.SetState(RUNNING)
|
2018-01-26 16:07:38 +01:00
|
|
|
// Create a new timer
|
|
|
|
timer := time.NewTimer(t.duration)
|
|
|
|
// Record our started time
|
|
|
|
t.started = pomodoro.Start
|
|
|
|
loop:
|
|
|
|
select {
|
|
|
|
case <-timer.C:
|
2021-08-31 00:31:35 +02:00
|
|
|
t.stopped = time.Now()
|
2018-01-27 16:58:56 +01:00
|
|
|
t.count++
|
2018-01-26 16:07:38 +01:00
|
|
|
case <-t.toggle:
|
|
|
|
// Catch any toggles when we
|
|
|
|
// are not expecting them
|
|
|
|
goto loop
|
|
|
|
case <-t.pause:
|
|
|
|
timer.Stop()
|
2018-01-27 16:58:56 +01:00
|
|
|
// Record the remaining time of the current pomodoro
|
2018-01-26 16:07:38 +01:00
|
|
|
remaining := t.TimeRemaining()
|
|
|
|
// Change state to PAUSED
|
2019-07-05 01:12:58 +02:00
|
|
|
t.SetState(PAUSED)
|
2018-01-26 16:07:38 +01:00
|
|
|
// Wait for the user to press [p]
|
|
|
|
<-t.pause
|
2018-01-27 16:58:56 +01:00
|
|
|
// Resume the timer with previous
|
2018-01-26 16:07:38 +01:00
|
|
|
// remaining time
|
|
|
|
timer.Reset(remaining)
|
2018-01-27 16:58:56 +01:00
|
|
|
// Change duration
|
2018-01-26 16:07:38 +01:00
|
|
|
t.started = time.Now()
|
|
|
|
t.duration = remaining
|
|
|
|
// Restore state to RUNNING
|
2019-07-05 01:12:58 +02:00
|
|
|
t.SetState(RUNNING)
|
2018-01-26 16:07:38 +01:00
|
|
|
goto loop
|
|
|
|
}
|
|
|
|
pomodoro.End = time.Now()
|
2019-01-25 06:04:41 +01:00
|
|
|
err := t.store.With(func(tx *sql.Tx) error {
|
|
|
|
return t.store.CreatePomodoro(tx, t.taskID, *pomodoro)
|
|
|
|
})
|
2018-01-26 16:07:38 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-01-27 16:58:56 +01:00
|
|
|
// All pomodoros completed
|
|
|
|
if t.count == t.nPomodoros {
|
|
|
|
break
|
|
|
|
}
|
2022-05-30 21:59:03 +02:00
|
|
|
t.SetState(BREAKING)
|
2018-01-27 17:42:13 +01:00
|
|
|
t.notifier.Notify("Pomo", "It is time to take a break!")
|
2018-01-27 16:58:56 +01:00
|
|
|
// Reset the duration incase it
|
|
|
|
// was paused.
|
|
|
|
t.duration = t.origDuration
|
|
|
|
// User concludes the break
|
|
|
|
<-t.toggle
|
|
|
|
|
2018-01-26 16:07:38 +01:00
|
|
|
}
|
2018-02-01 22:52:34 +01:00
|
|
|
t.notifier.Notify("Pomo", "Pomo session has completed!")
|
2019-07-05 01:12:58 +02:00
|
|
|
t.SetState(COMPLETE)
|
2018-01-26 16:07:38 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TaskRunner) Toggle() {
|
2022-05-30 21:59:03 +02:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
if t.state == BREAKING {
|
|
|
|
t.toggle <- true
|
|
|
|
}
|
2018-01-26 16:07:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TaskRunner) Pause() {
|
2022-05-30 21:59:03 +02:00
|
|
|
t.mu.Lock()
|
|
|
|
defer t.mu.Unlock()
|
|
|
|
if t.state == PAUSED || t.state == RUNNING {
|
|
|
|
t.pause <- true
|
|
|
|
}
|
2018-01-26 16:07:38 +01:00
|
|
|
}
|
2018-02-04 04:13:46 +01:00
|
|
|
|
|
|
|
func (t *TaskRunner) Status() *Status {
|
|
|
|
return &Status{
|
2021-09-01 14:56:22 +02:00
|
|
|
State: t.state,
|
|
|
|
Count: t.count,
|
|
|
|
NPomodoros: t.nPomodoros,
|
|
|
|
Remaining: t.TimeRemaining(),
|
|
|
|
Pauseduration: t.TimePauseDuration(),
|
2018-02-04 04:13:46 +01:00
|
|
|
}
|
|
|
|
}
|