refactor run loop, add simple libnotify support, improve notifications, binpack tomato image

This commit is contained in:
Kevin Schoon 2018-01-21 18:35:03 +08:00
parent cb5779343f
commit 85c4f20f8c
8 changed files with 454 additions and 21 deletions

15
Makefile Normal file
View File

@ -0,0 +1,15 @@
.PHONY: \
all
all: bin/pomo
clean:
rm -v bin/pomo bindata.go
bindata.go:
go-bindata -pkg main -o $@ tomato-icon.png
bin/pomo: bindata.go
mkdir bin 2>/dev/null
go build -o bin/pomo

235
bindata.go Normal file

File diff suppressed because one or more lines are too long

47
libnotify/libnotify.go Normal file
View File

@ -0,0 +1,47 @@
/*
libnotify is a lightweight client for libnotify https://developer.gnome.org/notification-spec/.
For now this just shells out to "notify-send".
TODO: Move this into it's own repository as time permits.
*/
package libnotify
import (
"fmt"
"os/exec"
"time"
)
type Notification struct {
Urgency string
Expire time.Duration
Title string
Body string
Icon string
}
type Client struct {
Path string
}
func NewClient() *Client {
return &Client{
Path: "/bin/notify-send",
}
}
func (c Client) Notify(n Notification) error {
var args []string
if n.Urgency != "" {
args = append(args, fmt.Sprintf("--urgency=%s", n.Urgency))
}
if n.Icon != "" {
args = append(args, fmt.Sprintf("--icon=%s", n.Icon))
}
if n.Expire > 0 {
args = append(args, fmt.Sprintf("--expire=%s", n.Expire.Truncate(time.Millisecond)))
}
args = append(args, n.Title)
args = append(args, n.Body)
_, err := exec.Command(c.Path, args...).Output()
return err
}

View File

@ -23,13 +23,15 @@ func start(path *string) func(*cli.Cmd) {
db, err := NewStore(*path) db, err := NewStore(*path)
maybe(err) maybe(err)
defer db.Close() defer db.Close()
task := Task{ task := &Task{
Message: *message, Message: *message,
Tags: *tags, Tags: *tags,
NPomodoros: *pomodoros, NPomodoros: *pomodoros,
Duration: parsed, Duration: parsed,
} }
run(task, &I3{}, db) runner, err := NewTaskRunner(task, db)
maybe(err)
maybe(runner.Run())
} }
} }
} }

99
task.go
View File

@ -3,10 +3,98 @@ package main
import ( import (
"fmt" "fmt"
"github.com/gosuri/uilive" "github.com/gosuri/uilive"
"io"
"time" "time"
) )
type TaskRunner struct {
count int
task *Task
store *Store
writer *uilive.Writer
timer *time.Timer
ticker *time.Ticker
notifier Notifier
}
func NewTaskRunner(task *Task, store *Store) (*TaskRunner, error) {
taskID, err := store.CreateTask(*task)
if err != nil {
return nil, err
}
task.ID = taskID
tr := &TaskRunner{
task: task,
store: store,
notifier: NewLibNotifier(),
writer: uilive.New(),
timer: time.NewTimer(task.Duration),
ticker: time.NewTicker(RefreshInterval),
}
tr.writer.Start()
return tr, nil
}
func (t *TaskRunner) Run() error {
for t.count < t.task.NPomodoros {
// ASCII spinner
wheel := &Wheel{}
// This pomodoro
pomodoro := &Pomodoro{}
prompt("press enter to begin")
// Emit a desktop notification
// that the task is beginning.
t.notifier.Begin(t.count, *t.task)
// Record task as started
pomodoro.Start = time.Now()
// Reset the timer
t.timer.Reset(t.task.Duration)
// Wait for either a tick to update
// the UI for the timer to complete
loop:
select {
case <-t.ticker.C:
t.updateUI(Message{
Start: pomodoro.Start,
Duration: t.task.Duration,
Pomodoros: t.task.NPomodoros,
Wheel: wheel,
CurrentPomodoro: t.count,
})
goto loop
case <-t.timer.C:
// Send a notification for the
// user to take a break. We record
// how long it actually takes for
// them to initiate the break.
t.notifier.Break(*t.task)
prompt("press enter to take a break")
// Record the task as complete
pomodoro.End = time.Now()
// Record the session in the db
err := t.store.CreatePomodoro(t.task.ID, *pomodoro)
if err != nil {
return err
}
// Increment the count of completed pomodoros
t.count++
}
}
return nil
}
func (t *TaskRunner) updateUI(msg Message) {
fmt.Fprintf(
t.writer,
"%s %s remaining [ pomodoro %d/%d ]\n",
msg.Wheel,
(msg.Duration - time.Since(msg.Start)).Truncate(time.Second),
msg.CurrentPomodoro,
msg.Pomodoros,
)
}
/*
// Task Starting.. // Task Starting..
// //
// 20min remaning [pomodoro 1/4] // 20min remaning [pomodoro 1/4]
@ -25,7 +113,7 @@ func display(writer io.Writer, msg Message) {
) )
} }
func run(task Task, prompter Prompter, db *Store) { func run(task Task, notifier Notifier, db *Store) {
taskID, err := db.CreateTask(task) taskID, err := db.CreateTask(task)
maybe(err) maybe(err)
writer := uilive.New() writer := uilive.New()
@ -36,7 +124,7 @@ func run(task Task, prompter Prompter, db *Store) {
var p int var p int
for p < task.NPomodoros { for p < task.NPomodoros {
pomodoro := &Pomodoro{} pomodoro := &Pomodoro{}
maybe(prompter.Prompt("Begin working!")) maybe(notifier.Begin(task))
pomodoro.Start = time.Now() pomodoro.Start = time.Now()
timer.Reset(task.Duration) timer.Reset(task.Duration)
loop: loop:
@ -51,11 +139,14 @@ func run(task Task, prompter Prompter, db *Store) {
}) })
goto loop goto loop
case <-timer.C: case <-timer.C:
maybe(prompter.Prompt("Take a break!")) maybe(notifier.Break(task))
fmt.Println("press enter to take a break")
pomodoro.End = time.Now() pomodoro.End = time.Now()
maybe(db.CreatePomodoro(taskID, *pomodoro)) maybe(db.CreatePomodoro(taskID, *pomodoro))
p++ p++
} }
} }
maybe(notifier.Finish(task))
writer.Stop() writer.Stop()
} }
*/

BIN
tomato-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -5,8 +5,9 @@ import (
"fmt" "fmt"
"github.com/fatih/color" "github.com/fatih/color"
"io/ioutil" "io/ioutil"
"os/exec"
"time" "time"
"github.com/kevinschoon/pomo/libnotify"
) )
// RefreshInterval is the frequency at which // RefreshInterval is the frequency at which
@ -115,22 +116,57 @@ type Pomodoro struct {
End time.Time `json:"end"` End time.Time `json:"end"`
} }
// Prompter prompts a user with a message. // Notifier implements a system specific
type Prompter interface { // notification. On Linux this libnotify.
Prompt(string) error // TODO: OSX, Windows(?)
type Notifier interface {
Begin(int, Task) error
Break(Task) error
Finish(Task) error
} }
// I3 implements a prompter for i3 // LibNotifier implements a Linux
type I3 struct{} // notifier client.
type LibNotifier struct {
client *libnotify.Client
iconPath string
}
func (i *I3) Prompt(message string) error { func NewLibNotifier() Notifier {
_, err := exec.Command( ln := &LibNotifier{
"/bin/i3-nagbar", client: libnotify.NewClient(),
"-m",
message,
).Output()
if err != nil {
return err
} }
return nil // Write the tomato icon to a temp path
raw := MustAsset("tomato-icon.png")
fp, _ := ioutil.TempFile("", "pomo")
fp.Write(raw)
ln.iconPath = fp.Name()
fp.Close()
return ln
}
func (ln LibNotifier) Begin(count int, t Task) error {
return ln.client.Notify(libnotify.Notification{
Title: t.Message,
Body: fmt.Sprintf("Task is starting (%d/%d pomodoros)", count, t.NPomodoros),
Icon: ln.iconPath,
})
}
func (ln LibNotifier) Break(t Task) error {
return ln.client.Notify(libnotify.Notification{
Title: t.Message,
Urgency: "critical",
Body: fmt.Sprintf("Time to take a break!\nPress enter at the console to initiate the break."),
Icon: ln.iconPath,
})
}
func (ln LibNotifier) Finish(t Task) error {
return ln.client.Notify(libnotify.Notification{
Title: t.Message,
Urgency: "critical",
Body: fmt.Sprintf("This task session is complete!"),
Icon: ln.iconPath,
})
} }

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bufio"
"fmt" "fmt"
"github.com/fatih/color" "github.com/fatih/color"
"os" "os"
@ -21,6 +22,12 @@ func defaultConfigPath() string {
return u.HomeDir + "/.pomo" return u.HomeDir + "/.pomo"
} }
func prompt(text string) {
reader := bufio.NewReader(os.Stdin)
fmt.Println(text)
reader.ReadString('\n')
}
func summerizeTasks(config *Config, tasks []*Task) { func summerizeTasks(config *Config, tasks []*Task) {
for _, task := range tasks { for _, task := range tasks {
fmt.Printf("%d: [%s] ", task.ID, task.Duration.Truncate(time.Second)) fmt.Printf("%d: [%s] ", task.ID, task.Duration.Truncate(time.Second))