refactor run loop, add simple libnotify support, improve notifications, binpack tomato image
This commit is contained in:
parent
cb5779343f
commit
85c4f20f8c
|
@ -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
|
File diff suppressed because one or more lines are too long
|
@ -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
|
||||||
|
}
|
6
main.go
6
main.go
|
@ -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
99
task.go
|
@ -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()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
66
types.go
66
types.go
|
@ -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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
7
util.go
7
util.go
|
@ -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))
|
||||||
|
|
Loading…
Reference in New Issue