From 85c4f20f8c71d4aa2971ca7b09b7d2d7ea1218f5 Mon Sep 17 00:00:00 2001 From: Kevin Schoon Date: Sun, 21 Jan 2018 18:35:03 +0800 Subject: [PATCH] refactor run loop, add simple libnotify support, improve notifications, binpack tomato image --- Makefile | 15 +++ bindata.go | 235 +++++++++++++++++++++++++++++++++++++++++ libnotify/libnotify.go | 47 +++++++++ main.go | 6 +- task.go | 99 ++++++++++++++++- tomato-icon.png | Bin 0 -> 3932 bytes types.go | 66 +++++++++--- util.go | 7 ++ 8 files changed, 454 insertions(+), 21 deletions(-) create mode 100644 Makefile create mode 100644 bindata.go create mode 100644 libnotify/libnotify.go create mode 100644 tomato-icon.png diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..90f1f99 --- /dev/null +++ b/Makefile @@ -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 diff --git a/bindata.go b/bindata.go new file mode 100644 index 0000000..8f4bcd9 --- /dev/null +++ b/bindata.go @@ -0,0 +1,235 @@ +// Code generated by go-bindata. +// sources: +// tomato-icon.png +// DO NOT EDIT! + +package main + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _tomatoIconPng = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x00\x5c\x0f\xa3\xf0\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x30\x00\x00\x00\x2b\x08\x06\x00\x00\x00\x3e\x13\x0b\xdf\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x21\x38\x00\x00\x21\x38\x01\x45\x96\x31\x60\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x01\x15\x08\x10\x11\xe3\x9e\xfd\x2f\x00\x00\x0e\xe9\x49\x44\x41\x54\x68\xde\xbd\x99\x7b\xb0\x5f\x55\x75\xc7\x3f\x6b\xef\xf3\xf8\x3d\xee\xcd\xcd\xbd\x24\x04\x12\xc2\x0d\x24\x24\x40\x54\x10\x82\x48\x40\x8c\x3c\x1a\xc1\x57\xcb\x8c\xd8\x8e\x56\xb1\x6a\x47\xc4\x57\xa1\x0e\x95\x69\xad\x45\x6a\xd5\xce\x50\xc5\x0a\x4c\x61\x46\x45\xab\xad\x45\xda\x29\x1d\x6d\xb4\xf1\x0f\xd4\x0a\x02\xa6\x3c\x42\x0c\x90\xc7\x0d\x49\x48\xb8\x79\xdd\xdc\xdf\xeb\x3c\xf6\x5e\xfd\x63\x9f\xdf\xbd\x37\x0f\x2c\x81\xb6\x67\xe6\xcc\x39\x73\xce\xde\xfb\xac\xef\x7a\x7c\xd7\x5a\xfb\xc0\xcb\x38\xde\xf9\xe1\xf3\xb8\xfe\xf3\x97\xcb\xe1\xcf\xef\xbe\xff\xa3\xe7\xf2\xff\x7c\x98\x97\x33\xe9\xf4\xe5\xc7\x9f\x76\xeb\x4d\x3f\xd6\x99\xcf\xee\xfc\x97\x3f\x34\xe0\xdf\x75\xf8\xd8\xb3\x2e\x58\xf4\x92\xd6\xfc\xe2\x5d\x57\xfd\xdf\x03\xb8\xe5\x8e\x77\xbc\xfa\x73\x77\xbe\xfd\xc2\xa7\x37\xec\xd9\xf4\x27\xb7\xbe\xf9\xd0\x97\x4e\x4f\x2c\x8b\xe2\x75\x33\x1f\x5d\x7c\xe5\x19\x3c\xf6\x8b\xad\x9c\x7f\xc9\xd2\x5b\x56\xbd\x75\xf9\xd6\x37\xbe\xfd\xcc\xe3\x01\x3e\xfd\x57\x57\x02\xf0\xf9\xdb\x7f\xc7\xdc\x72\xfb\x6f\x37\xbe\xf8\x77\x57\x99\x1b\x3f\x74\xdf\x51\xbf\x79\xdd\x9f\xad\xb2\xbf\x49\x26\x79\xa9\xc2\x7f\xe5\xdb\xef\xe2\xc0\xfe\xee\x95\xdb\x77\x1c\x58\x70\xd7\x17\x1e\xb8\xeb\x9a\x1b\x56\x32\x7f\xeb\x04\x9f\xff\xfe\x7a\x00\xbe\xfc\xed\xf7\xcd\x01\xc6\xbd\xf3\x0b\xaf\x7f\xdf\xb7\xb6\xf7\xe7\x5d\xf4\xe6\xd3\x3f\x10\x27\xe6\xee\xa4\x66\x37\xad\xf9\xde\x13\x4b\xfa\xcf\x6f\xfa\xc2\x95\x75\x1b\x9b\xd1\xcf\xdd\xf0\x6f\xbf\x3e\xda\xf7\x3e\x70\xc3\x1b\x68\x34\x92\x18\xc1\x7c\xf5\xe6\xb5\xd9\x2b\x06\xd0\x3f\x6e\xf8\xdc\xe5\xef\x9f\x68\xf5\xce\xb9\xfb\x8b\x3f\xfd\x18\xc0\x37\x41\x46\x41\x57\x01\xb7\x7c\xed\xea\xcd\xde\xf9\x9d\x9f\xf9\xf8\xbd\x17\x01\x5c\x79\xd5\xab\x86\x34\x36\x9b\xe3\xd4\x8e\x44\xc2\x79\xf7\x7d\x73\xdd\x23\x00\x9f\xf8\xf4\xa5\x57\xa7\xa9\xdd\xf3\xa5\xcf\xfe\xe8\x27\x3b\x41\x9e\x04\x3d\x01\x78\x4d\xf5\x8d\x3b\x2e\x3d\x95\x6b\xd7\x6e\xe6\x83\x9f\xba\x78\x71\x6c\xec\x58\x5c\x33\xe5\x6d\x7f\xb1\xf6\xe5\x03\x78\x16\x58\x02\xdc\xfe\xee\x73\xf9\xc8\xdf\x3f\xca\x87\x6f\x5c\xf5\xd5\xdc\x95\xe7\x0d\xb5\xf3\x2b\x6f\xbd\xe3\x97\xfb\x5f\x58\x7e\x72\x74\xfc\xfa\x6d\xee\xcf\xff\xf4\x8a\xd7\xb7\x2d\x3f\xf7\x5e\x9f\x59\xb0\xa3\x75\xfe\x83\xae\x7d\x95\x8f\xcc\xdd\xd6\xc8\xee\x8b\xc6\xf3\xa5\x1f\xff\xe1\xee\xce\x27\x7e\x7f\xf1\xf9\x79\x2d\xba\xf6\x8e\xbf\x7d\xe0\xbd\xe3\xe7\x2e\x4b\xf0\x28\xbe\x54\x2d\x32\xcf\xf8\x7e\x9d\x9c\x2c\x74\x49\x2f\xf7\xef\xbd\x6e\xe5\x1c\x63\xcd\xaa\x6f\xdc\xf6\xb3\x7b\x5f\x91\x0b\x3d\x0d\x2c\x05\x9e\x02\x99\x9d\x20\x66\xe9\x22\x99\xf7\x64\xce\x87\xaf\x3b\xe5\x33\x45\x5d\xae\x1f\x2c\xf4\xfa\x2f\x7f\x65\xc3\x77\x38\x7f\xc4\x72\xa0\xe7\x3f\xf0\xc6\xd1\x3b\x4a\x2f\xef\x51\xef\x77\x76\x6d\x9e\x02\xc7\x59\xe5\xf1\x9b\x9f\x9e\xbc\xec\x9e\x39\x03\xaf\xdf\xd4\x8c\xef\x3b\x2d\x2f\xdf\x72\xf3\xd8\xbe\x87\x11\x51\xac\xf7\xa8\x78\xd4\x78\x9e\xee\x79\xd9\xb3\xbd\xf7\xbb\xef\x7d\xdd\xb5\x02\xbb\xbe\x7b\xcf\x2f\xff\xd9\x01\x7b\x80\x79\xc7\x0a\x60\x0c\x58\x87\xe1\xf5\x78\xca\xe3\x1a\x26\x9e\x3b\x62\xa2\xfa\x40\x34\x62\x0b\xf3\x5c\xcf\xcc\x6e\x77\x38\xf5\xde\xd3\x9a\x5f\xda\xbc\xa4\x7e\x41\x33\x36\x0f\x5d\xf2\xd0\x44\xf7\xe4\xdd\x45\xbd\x1d\x61\x6f\x5b\x3e\xb0\xc2\xc5\x10\xcd\x16\x50\x18\xee\xfa\xbd\xef\x79\xb8\xb3\xee\x2b\x4b\xea\x17\x78\xd1\xe6\x35\x5b\x32\xd2\xd2\x33\x52\x82\xa8\xd2\xa9\xd9\xf1\x9d\x0d\xf3\xf8\xd8\x70\xf4\xd8\x84\x70\xe2\x85\xe3\xdd\x4f\xbe\x75\xdd\x96\x3d\xcf\xbf\xe6\x24\x6b\x77\xee\x76\xcf\xef\xe9\xf8\xb3\x81\xfd\xc0\xf0\xb1\x58\x60\x1b\x48\xed\xec\x53\x2c\x3e\x8e\x53\xa3\xf1\xbe\x82\x39\xbb\x7a\xe6\x26\x2f\xbc\x33\x86\xba\x15\xa2\x71\x8b\xdc\x73\x46\x9d\xc6\x69\x03\x0c\xf4\x3c\xbf\xf5\xab\x83\xfc\xa2\x9b\xf3\xc8\x29\x4d\x9a\x23\x36\x00\xe8\x79\x0e\x3e\x97\xf3\x42\x04\x83\x85\x52\x02\xef\x9e\x74\x14\x0b\xea\xac\x9b\x93\xb0\x6f\xbc\xc7\x3b\xc6\x32\x16\x64\x0a\x8e\xd2\x29\x7b\x53\xab\x1f\x5b\x31\xe2\x7e\x30\xde\xb5\x4e\x7d\xa7\x48\x9e\xdc\xee\x87\x41\x5f\x92\x05\x36\x01\xb5\xb9\x75\x13\x9f\x30\x3f\x9a\xbb\x70\x48\x1e\xdf\xd4\x3e\xbb\x5d\x72\x3d\xc2\xd5\xb1\x80\xb5\x50\xa0\x6c\x3a\xb1\xce\x9e\x0b\x47\x71\x93\x05\x3f\xd8\xb0\x03\xaf\x8a\x4b\x84\x46\xe6\x29\x13\x43\x63\x24\x22\xaa\x1b\xbc\x42\xb6\xcf\x91\xd6\x2c\xc9\x40\x00\x35\x99\x7b\x16\xef\xca\x58\xb5\x3b\x63\x45\xe6\x29\x10\x72\x85\x42\x21\xf7\x42\xee\xc1\x2b\x0f\xa7\x46\x6f\x5c\xb1\x32\x7b\xe0\x85\x47\x0d\xf1\xfa\x2d\x7e\x04\xf4\x39\x60\xe1\x8b\x01\xd8\x04\xd2\x9c\x9d\x4a\xf4\x9a\xb3\x63\xe6\xcf\x1f\xda\xbc\x61\xec\x8f\xcb\x4e\xf7\x53\x89\x78\x62\x03\xa9\x35\x48\x22\xfc\xe4\xcc\x59\x8c\xc4\x09\xe7\x3f\x33\xc1\x3f\xfa\x92\x35\x0d\xa1\x4f\xd8\x3a\xe3\x3a\x70\x42\x42\x3a\x68\xa9\xa5\x09\x07\x77\x75\x69\x8f\x67\x24\xc0\x15\x07\x4a\xce\x29\x94\x93\x44\xb0\x0a\x8a\xe2\x14\x4a\x85\xc2\x43\xee\xa1\xe7\x85\xdc\x81\x08\x37\x9e\xd6\x74\x5f\xc3\xb9\x2c\x5b\xbf\xc5\x2d\x9c\x61\x89\x29\x00\x7b\x80\x39\xc0\xce\xe1\xc4\xd4\xce\x59\x91\x74\x47\x97\x2c\xd8\xf1\xeb\xb1\xef\x49\xb7\x7d\x4e\xdc\x6b\x93\x88\x52\x4b\x0c\x71\x2d\xc6\x0d\x24\x34\xa2\x88\x24\xb6\x6c\x68\x75\x69\x95\x9e\x8e\x01\xa7\x50\xa8\x67\x20\x89\x88\x54\x69\xe7\x05\x59\x59\x82\x15\x4c\xcf\x31\x0b\x38\x4e\x84\x05\x6a\x49\xa3\x88\xae\x31\xf4\xbc\xc7\xbb\x92\x86\x73\x44\x4e\xf1\xaa\x53\x20\x32\x0f\x5d\x17\x80\x78\xe5\x6f\x2e\xde\xbc\xf1\xfa\x17\xce\x9c\x6f\xf7\x3f\xb5\xd3\x0d\x02\xf3\x81\x08\xe0\xd1\x4a\xf8\xf1\x54\x24\x7a\xd5\xab\xa3\xf6\xc9\x4b\x16\xee\x1a\xdb\xfd\xa3\xb4\x16\x2d\xb2\xed\x82\xd8\x28\xb5\x48\x88\x1b\x31\xf1\xec\x26\x76\x78\x00\x33\x50\x47\x93\x88\xe1\x76\x97\x7a\x5e\x52\x38\x8f\xa8\xb2\x70\x78\x90\xba\x2a\xae\xd5\x41\xf7\xb7\xd1\x89\x36\xda\xcd\xd0\x52\x41\x04\x92\x04\x33\x38\x08\x8d\x06\xcd\x24\xa1\xe9\x3c\xf4\xba\xb8\xc9\x49\xb4\xdb\xc1\x38\x87\xf1\x60\x6c\x28\x13\xa4\xb2\x65\xcf\xc9\x1f\xfd\x6c\xf1\xb2\x2d\x26\xd2\xbb\x66\xcd\xad\xeb\x83\xe3\x3d\x0f\x1a\x00\x9c\x0b\x6c\x04\xf1\xcb\x4e\xb5\x43\x97\x5c\xd2\x7c\xf6\xe7\x8f\xff\xb0\xd6\xac\x2f\xb2\xe3\xbb\x88\xcb\x8c\xc4\x28\x49\x64\x89\x6a\x31\x76\x56\x03\x3b\x32\x80\x0c\x35\x21\x8d\x39\x29\x1a\xe1\x60\xa7\x47\xb7\x93\xd3\xa8\x27\x34\x6a\x31\xda\x2b\x90\xd8\x42\xe9\xf1\xdd\x1c\xcd\x0a\x54\x14\xac\xc5\xd4\xeb\xc8\xf0\x30\x32\x34\x8c\x24\x09\x78\x87\xb6\x5b\x18\x55\x7c\x51\x80\xf7\x58\xf1\xa0\x20\x36\x38\x89\x56\xee\xd8\x75\xfc\x75\xcf\xf9\xef\xd6\x8e\x9f\x3b\xb1\x60\x7c\x9b\x02\x6a\x00\x7e\x88\x30\x30\xb7\x26\xb5\xa5\x67\xd4\xd6\x3d\xf8\xd4\x5f\xd6\x46\x86\x17\x47\x79\x46\xdc\x69\x91\xe2\x49\x8c\x62\xc4\x23\xde\x43\x51\xa0\x79\x81\xf6\x72\xc8\xc3\xfd\x60\x1c\x33\x54\x4f\x68\xc6\x16\xcd\x0a\xc8\x8a\xf0\xae\x70\xe0\x3d\x68\xe5\xb2\xaa\xe0\x3d\x5a\x3a\x70\x05\x94\x05\x94\x25\x5a\x3a\x54\x3d\x33\x49\xc6\x08\x58\x20\xb6\x90\x1a\x48\x8d\x12\x0b\xe9\xa6\xae\xfd\xce\x71\xcb\x22\x3f\xda\x40\x66\xf7\x2d\xf4\x04\xc8\x82\xb3\x17\x47\xf9\x05\x97\x8e\x3e\xbf\x73\xef\x33\x8d\x81\x3a\x66\xf3\x33\x24\x93\xfb\x89\x45\xb1\x22\x88\x15\x24\x89\x91\x66\x0d\x33\x58\x47\x1a\x29\x92\x44\x10\x9b\x60\x6f\xd1\x10\x04\xa5\x43\xb3\x12\xdf\xe9\xa1\xad\x2e\xda\xca\xd0\xbc\x0c\x40\x44\x90\x28\x42\x1a\x4d\xa4\xd1\x40\xe2\x18\xf5\x1e\xcd\x72\xb4\xdb\x41\x7b\x3d\x70\x65\x00\x5a\x81\x76\x0a\xb9\x0a\x5d\x07\xad\x12\xba\x4e\x7c\x62\x58\xb1\x28\xe9\x3d\xd1\x79\x6a\xac\x8c\x6e\x03\x86\x07\x90\xe1\x0f\xbd\x8b\x47\xee\x7d\xe4\xeb\x03\x27\x2f\xc0\xec\xdf\x43\xd4\x6d\x13\x09\x58\xa9\x8c\xe8\x09\x9a\x57\x8f\x66\x05\x12\x9b\xe0\x26\x91\x0d\xea\x12\xc0\x87\xe8\xd3\xa2\x44\x0b\x0f\x45\x19\xac\xa0\x0a\x62\x40\x40\xbd\x42\xa7\x83\xe6\x19\x12\xc5\x60\x4d\xa5\x78\x45\x22\x0b\x28\xea\xfa\xd6\x50\x0c\x1a\x2c\x61\xc2\x99\x2b\xa6\xf0\xac\xaa\xc5\xc9\xfa\x6d\x31\x12\x5d\x0a\xd8\x79\xf3\xcc\x53\xf7\xff\xea\x55\x76\x70\x60\x65\x64\x0d\x32\x71\x80\xc8\x15\xd8\x99\x34\xa5\x01\x04\x85\x47\x5d\x06\x85\x81\x58\x82\x8d\x8d\x09\x1f\xec\x03\x28\xc3\x89\x56\x0b\x88\x41\x4c\x7f\x5c\xdf\x3f\x22\x88\x63\x48\x12\xc4\x58\xc4\x3b\x34\xcf\x03\xb0\xbc\x40\xbd\x03\xaf\x88\x2a\x56\x15\xeb\x95\xb8\x72\x2b\x27\x5c\x36\x6b\x56\xfe\xd5\xfa\xec\x21\x89\xa2\x08\x89\x06\x87\x4d\xe6\xe5\xa2\x74\x68\xd0\x48\xd6\xc3\x76\x5a\x18\x14\x11\x3d\x32\xcb\xa9\x0f\x52\x59\x50\x6b\x90\x58\x90\x48\x40\x25\x08\xee\x05\xdc\x34\x4b\x8b\x11\x88\x62\x24\x4e\x20\x8a\x10\x63\x02\x1b\x45\x31\x34\xea\x98\x7a\x13\x92\x38\xb8\x5e\xb7\x8d\xb6\x5a\x68\xaf\x87\x14\x39\xea\x1c\x38\x1f\x98\x49\x3d\x46\x35\x18\xac\x64\x05\xc3\xc2\x70\x14\x49\x64\x63\x2b\x73\xce\x38\xc5\x3c\xb7\xdf\xbe\x31\xaa\xd5\x90\xf1\xe7\xb1\x65\x5e\x79\x85\x4c\x07\x96\x4c\x47\x97\x44\xc1\x9e\xa6\x6e\x91\x7a\x15\x07\x0a\xe4\x0e\x28\x50\x27\x88\x17\x54\x4d\x10\x34\xad\x05\xbf\x4f\x93\x90\xc6\xc5\x20\x49\x82\x0c\xce\x0a\x94\x9a\xa4\x50\x16\xf8\xc9\x49\x34\x9d\xc0\xb7\x26\x21\xcf\x2a\xc2\xc8\x91\x3c\xc7\x6a\x81\xf5\x0e\x83\x82\x70\x3c\x13\xc6\x26\x43\x49\x19\xc5\xd6\x08\xe7\x9e\x25\xfc\xe4\xa9\x73\x8d\x11\xa4\xd3\x01\xe7\x2a\xf2\x92\x43\xab\x0e\x21\x04\x73\x64\x20\xb5\x48\x33\x42\x9a\x31\x92\x58\xd4\x29\x74\xa5\x4a\xa5\x8a\x3a\x10\x0c\x92\xa4\x48\xb3\x89\x99\x35\x04\xf5\x06\x12\x47\x60\x0c\x52\xab\x23\x43\x43\x98\xa1\xd9\x48\x92\xa2\x65\x89\x1c\x9c\xc0\xa7\x29\xd2\xa8\xa3\xdd\x2e\xe4\x19\xda\xe9\xe0\xda\xed\x40\xab\x4e\x11\xe3\x31\xa5\xb2\x7e\x57\x7c\xd2\xe2\x86\xdf\x1a\xd5\xe6\x0c\xc0\xbc\x13\x2c\xb2\x61\x91\x38\x07\x59\x2f\xb8\xcf\x4c\xed\x4f\x61\x90\xe0\xbf\x91\x41\x52\x8b\xd4\x63\xa4\x19\x43\x62\x90\x22\x14\x2f\xf4\x4a\xd4\x84\x71\x82\x85\x24\x46\x1a\x0d\x18\x1c\xc4\x0c\x0c\x42\x1c\x57\x4c\xd4\x40\x66\x0f\x07\x00\x69\x0d\x2d\x0b\x7c\x9a\x62\xa2\x08\xad\xd5\xd0\x6e\x17\xed\x75\x21\x4e\x30\x08\xb6\xf4\x98\xdc\x05\xb7\x16\xa5\xe7\x19\xb6\xea\xc7\xa2\x7a\xa3\x2e\x3b\xfe\xe3\x91\xe3\xc5\x5a\x28\x4b\x28\xf2\xa9\xe0\x3b\x02\x82\x04\x10\x81\x52\x6d\x00\x51\xb3\x15\x95\x0a\xf4\x1c\xbe\x02\x29\xc6\xa0\x62\x31\x71\x8c\xa4\x35\x4c\xbd\x01\x8d\x3a\x92\xd4\x30\x49\x02\x8d\x66\x10\x7e\xd6\x10\x52\xaf\x23\x79\x0e\x22\x78\xf5\x61\xad\x34\x85\x76\x1c\x34\x5f\xe4\xd0\xed\x81\xcd\x10\x09\xc5\x91\xaa\x46\x46\x3d\x51\x1a\x45\x3a\xb1\xf7\xe0\x5c\x13\xa7\x68\x59\x4c\xb9\xcf\x51\x85\xef\x5f\x6d\xc5\xaf\x91\x40\x6c\x90\xc8\x86\xd8\xb6\x21\x68\xa7\xe6\x19\x83\x58\x8b\xc4\x31\x12\xc7\x10\xa7\x21\x0e\xd2\x5a\x10\xba\x56\x0b\x96\x48\x6b\x68\x14\x21\x45\x81\xa9\xd5\xf0\x79\x11\x58\xcf\x79\xc8\xf3\x30\x2f\x9a\xc1\x62\x80\x35\xb4\xbd\x58\x35\xd6\x1a\xf1\xae\xac\x85\xac\x51\x65\xce\x17\x2b\x55\x65\xe6\x29\xd3\x8c\x22\x87\x0f\x96\xca\xe3\x24\xf0\xbf\x39\xda\x69\x43\x40\x57\x6b\x48\xf5\x5c\x4d\x3f\xaf\x54\x56\xad\xd6\xf7\x48\xc8\x6f\xd5\xda\x23\x91\xdf\xdd\x2d\x55\x0c\x06\x35\x51\xd4\x53\xf5\xa8\x57\x54\xa7\xf5\x7e\x04\x89\xf6\x8b\x12\x1f\x92\x96\x7a\x5f\x5d\xab\x1c\xa0\x87\xce\xd4\x2a\xa3\xaa\xf7\xa8\x2a\xd2\x1f\xe7\x5d\xc8\xb8\x45\x28\x25\x70\x25\x5a\x84\xd2\x42\x8a\x02\xf2\xbc\xca\x09\x39\x5a\xe4\x68\x59\x82\x73\xa8\x06\xf9\xbc\xc2\xe8\x68\xb1\x3f\xcb\x32\x8d\x26\x5b\x6d\xe6\x2e\x3c\x61\xfb\xf6\xb1\xdd\x68\x62\x0f\x91\xd1\x1c\x92\xc8\x2a\xdd\x6a\xc8\xa6\x52\x7a\xb4\xf0\x48\xee\x10\x05\x5f\x56\x0a\xf0\xd3\xdd\x80\x54\xc2\x8b\x73\xa1\xe6\x29\xf2\xe0\x62\x55\x66\xf6\x69\x82\x89\x93\x00\x20\x2f\xd0\x76\x0b\xdf\x9a\x44\x5b\x93\x68\xbb\x0d\xdd\x0e\xda\x6e\xa3\xdd\x2e\x3e\x2f\x70\xce\xe1\x54\x91\xd0\xd3\xd0\x9d\xc8\x88\x5a\x7b\xbb\x9c\xf4\x96\x0b\xf6\x6d\xbb\xed\xde\xcc\xf9\x34\x15\x31\x95\x32\x15\x3d\xdc\x87\x7c\xf0\x4b\x29\x3c\x9a\x39\xe8\x96\x78\x2b\x48\xaa\x50\x7a\x28\x3c\xe2\x74\xda\x92\xce\x05\x2d\x67\x59\xa0\x45\x6b\x43\x01\x67\x2d\x9a\xe7\x18\x25\x58\x27\x4d\x21\x2f\xf0\x13\x07\xd0\xbd\x7b\xf1\x13\x07\xf0\x9d\x36\x74\xbb\x68\xbb\x8d\x6b\xb5\x70\x59\x86\x2f\x1d\x2e\x78\xf8\x63\x4c\x2a\xde\xab\x46\x9d\x89\x96\xf2\xc4\x13\x1e\xe7\x1f\xf5\xce\xaf\x74\x18\x4a\x95\x6a\xcb\x4e\x31\x32\x03\x85\x0a\xea\x41\x4a\x45\x72\x8f\x74\xca\xc0\xac\x55\xff\xe7\x7b\x65\xc8\xc6\xce\x87\x2a\x0c\x87\xe6\x19\xbe\xd3\x09\x81\xec\x1c\x12\x45\x28\x60\xe2\x18\xdf\xeb\xa2\xdd\x4e\x95\x07\x0a\x74\xe2\x00\x7e\xdf\x5e\x74\x62\x02\xed\x74\xf0\x79\x86\xf6\x32\x7c\x2f\xa3\xcc\x73\x4a\xe7\xf1\xde\x63\xe0\xa7\x93\xad\xc4\xec\x9e\x98\x28\xa3\x7d\x05\xda\x5e\xfb\xa0\xa2\x83\x3f\x2a\xb2\x6c\xa5\x8a\xc5\x68\x70\x1f\x45\xa6\xdd\xa8\x9f\xd7\x9c\x22\xa5\x43\x7a\xa1\xc8\x13\x07\x3e\xae\x8a\xaf\xdc\xa1\x3d\x87\x96\x5a\xd5\x42\x82\x98\x1c\x6d\xb7\x43\x9c\xf7\x7a\xa1\x26\xc2\xe3\x4d\x84\x69\xb5\x60\xb2\x85\x24\x31\x14\x21\x13\xfb\x89\x03\x68\x6b\x32\x58\xad\x28\x70\xa5\xc3\x15\x25\x65\xe1\x29\x4a\xa5\x54\x7c\x04\x0f\x75\xf3\xd2\x0d\x17\x68\x34\x0f\x74\x72\xeb\x33\x3e\x1a\x7d\xed\xda\x7c\xb2\xfd\x59\x05\xf0\x02\xa2\x81\xde\x75\x06\x83\x1a\x05\x27\x53\x5d\x92\x78\x17\xac\x61\x2b\x7a\x2b\xb5\xaa\x42\x83\x05\x14\x8f\x64\x05\xe2\x3b\x48\xe9\x42\x2d\xd4\xb7\xa8\x18\x24\x9d\xc4\xd4\x0e\x06\x1a\x76\x2e\x24\xaf\x4e\x3b\x94\xd5\x45\x81\xf7\x0e\xe7\x94\xdc\x29\x59\xa9\x14\x1e\xbc\x67\xcf\xeb\x8e\x73\x0f\xbd\xb0\x7d\x97\xdf\x0e\xc8\xfd\xc0\x59\x20\x0b\x55\xe5\xe7\xaf\x3d\xff\x09\xab\xee\xcc\xb8\xdb\x21\x15\x37\x95\x9f\xa6\x58\xb2\x4f\x9f\x80\xa9\x72\x81\x58\x99\xe6\xe7\x2a\x46\x70\x15\xdb\x54\x33\xc5\x9a\xd0\x8d\x19\x13\xe2\x4a\x41\x4c\xe8\x0d\x88\xe2\x90\xf4\x34\x34\x4b\xfd\xfa\x27\x30\x17\x94\x5e\xe9\x39\xe8\x39\xa5\x1b\x94\xf7\x7b\x2b\x17\xb4\xfe\x69\xec\xa7\x3b\x74\x11\xf8\xe8\x6d\xc0\x66\x40\x2f\x5c\x2e\x43\x83\xc7\xbd\x7d\xdf\xae\x7d\xcf\x7a\xaf\xa8\x11\x1c\x60\xd1\x29\x10\x7d\x2b\x08\x02\x1a\xdc\x47\x44\xc1\xf8\x69\x86\xd2\x50\x7a\x8b\x07\x24\xb8\x51\x18\xeb\xa7\xb5\x4f\xe8\x8f\x45\x0a\x30\x59\xb8\x57\x42\x7b\xe9\xdd\x94\xf0\xde\x2b\x85\x42\xcf\x43\xe6\x05\x07\xeb\xce\x1b\x2a\xff\x75\xf7\xd6\x8c\x6b\x02\x51\x22\x5b\x80\x53\x80\xed\x35\x64\xf6\xf2\x65\xd1\xc3\xfb\xe5\x56\x54\x3f\x1a\x87\x92\x07\x2b\x15\x9d\x4a\x00\x32\x6d\x88\xca\x95\x4c\xbf\x6a\x0d\x31\xa1\x3a\xdd\x88\x33\xc3\xfd\x10\x39\x2c\x39\x56\xc5\xa1\xc8\x94\x55\x50\x0d\xec\xe7\x15\xad\xba\xb1\xb0\xc5\x12\xf6\x8c\x2c\x5c\xb1\x74\x96\x5f\xdb\xfe\xaf\x67\xca\x53\x2b\x62\x97\xfe\x46\x56\x01\xcc\x1e\xb0\x66\xde\xaa\x45\xac\x7d\x32\x7a\xce\x08\xf3\xfb\xc2\xf7\x2d\x30\xe5\x4e\x87\xb9\xd5\x21\xd7\xbe\x82\x5f\x6c\xf7\xec\x45\xf6\x02\x75\x1a\x73\xc8\x75\x15\x00\xe7\xa1\x0c\xd3\x3e\x79\xf1\x65\x93\xb7\xbd\xf0\xf5\xe7\x65\x7d\xa1\x7e\x3e\x70\xfa\xe1\xcb\x1d\x00\x86\x46\x90\xff\x1c\x5e\x36\xdc\x29\x59\x2f\x86\x13\xcc\x74\x37\x18\xa8\x55\x42\x60\x73\x58\x05\x71\x48\x9c\x4c\x2b\xfc\x48\xb9\x95\x23\x32\xbd\xce\x00\xd0\x6f\x26\xfb\x89\xdd\x85\x41\xdf\x7a\xd3\x3c\xff\x07\x3b\xb6\xed\xf5\xed\x9d\xfb\xfc\xb2\x17\xd3\xc7\x58\xb5\x95\x7e\x16\xc8\x63\xa3\x4b\xe6\x3a\xb1\xcf\x09\x24\x1c\x5a\x02\xcd\x2c\x4c\x8f\xa8\xf3\x8e\x26\x7c\xff\x9d\xfe\x06\xad\xeb\x8c\xce\x55\xa7\x3d\x0a\xe0\x1b\x97\x6d\xdd\xf8\xfe\x5d\x16\x53\x73\xa1\x52\x1b\x79\xa9\x9b\xbb\x3f\x3e\x79\xe9\x5c\x2f\xb2\x16\x78\xf5\x4c\xa1\x84\xa3\xbb\x88\x1c\xe3\x6f\x13\x7d\x11\x6b\xcc\x48\x3b\xd7\x5d\xbe\x75\xe3\xed\xbf\x8c\x22\x29\xcb\x52\x57\xbe\xdc\x1f\x1c\x6b\x46\x97\xdd\x0d\x5c\x53\x15\xd2\x47\xcc\xfc\x9f\x16\x39\x9a\xf6\x8f\x1a\x03\xd3\x37\x5b\x54\x79\xdb\xea\x6d\x1b\xd7\xf7\x1f\x3f\x00\x5c\x7c\xac\x3f\xf9\xd6\x8c\x2e\x63\xcd\xe8\x32\x56\x8f\x6d\xfc\x20\xb0\x1c\x78\x76\xa6\x79\xfb\xa7\x3f\xca\x79\xf8\xfb\xdf\x34\xf6\x90\xf1\xf0\x6e\x0f\x67\xae\xde\xb6\x71\xfd\xbf\xbf\xed\xfd\xfc\xb8\xff\xc3\xf0\x95\xfc\x23\xab\x40\xf4\xef\xaf\x03\x3e\x02\x9c\xf9\xbf\xf8\xbb\x77\x03\xf0\x0f\xab\xc7\x36\xde\x7c\xf8\xf7\x78\x09\xd6\x3d\xe6\x63\xcd\xe8\x32\x53\xfd\x36\xbb\x13\x78\xd3\x2b\x10\xfc\xfb\xc0\x4d\xc0\xa6\xd5\x63\x1b\xdd\xb1\x0a\x4f\x7f\x77\xfa\x65\x1c\x7e\xf5\xd8\xc6\xa7\x81\x4b\xd6\x8c\x2e\x9b\x05\xbc\x01\x78\x6d\x45\xcd\x27\x03\x73\x81\x05\x40\x13\xd8\x05\x8c\x03\xbb\x81\x2d\x95\xb6\xd7\x01\xbf\x58\x3d\xb6\xb1\xe8\x0b\xdd\x3f\x8e\x45\x78\x80\xff\x06\x26\x17\xf9\x29\x2d\x26\x91\x99\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\x01\x00\x00\xff\xff\x8e\x30\x0d\x01\x5c\x0f\x00\x00") + +func tomatoIconPngBytes() ([]byte, error) { + return bindataRead( + _tomatoIconPng, + "tomato-icon.png", + ) +} + +func tomatoIconPng() (*asset, error) { + bytes, err := tomatoIconPngBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "tomato-icon.png", size: 3932, mode: os.FileMode(420), modTime: time.Unix(1516522577, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "tomato-icon.png": tomatoIconPng, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "tomato-icon.png": &bintree{tomatoIconPng, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/libnotify/libnotify.go b/libnotify/libnotify.go new file mode 100644 index 0000000..60d91fd --- /dev/null +++ b/libnotify/libnotify.go @@ -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 +} diff --git a/main.go b/main.go index 0b2cd8c..519d1ec 100644 --- a/main.go +++ b/main.go @@ -23,13 +23,15 @@ func start(path *string) func(*cli.Cmd) { db, err := NewStore(*path) maybe(err) defer db.Close() - task := Task{ + task := &Task{ Message: *message, Tags: *tags, NPomodoros: *pomodoros, Duration: parsed, } - run(task, &I3{}, db) + runner, err := NewTaskRunner(task, db) + maybe(err) + maybe(runner.Run()) } } } diff --git a/task.go b/task.go index 685717c..c3f8bfe 100644 --- a/task.go +++ b/task.go @@ -3,10 +3,98 @@ package main import ( "fmt" "github.com/gosuri/uilive" - "io" "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.. // // 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) maybe(err) writer := uilive.New() @@ -36,7 +124,7 @@ func run(task Task, prompter Prompter, db *Store) { var p int for p < task.NPomodoros { pomodoro := &Pomodoro{} - maybe(prompter.Prompt("Begin working!")) + maybe(notifier.Begin(task)) pomodoro.Start = time.Now() timer.Reset(task.Duration) loop: @@ -51,11 +139,14 @@ func run(task Task, prompter Prompter, db *Store) { }) goto loop 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() maybe(db.CreatePomodoro(taskID, *pomodoro)) p++ } } + maybe(notifier.Finish(task)) writer.Stop() } +*/ diff --git a/tomato-icon.png b/tomato-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a4ed47e705db75f8f9db8da93d4b9ced83113b65 GIT binary patch literal 3932 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