Commit
+146 -84 +/-3 browse
1 | diff --git a/main.go b/main.go |
2 | index f8a6891..f283bc7 100644 |
3 | --- a/main.go |
4 | +++ b/main.go |
5 | @@ -1,7 +1,9 @@ |
6 | package main |
7 | |
8 | import ( |
9 | + "database/sql" |
10 | "encoding/json" |
11 | + "fmt" |
12 | "os" |
13 | "sort" |
14 | "time" |
15 | @@ -30,6 +32,14 @@ func start(path *string) func(*cli.Cmd) { |
16 | NPomodoros: *pomodoros, |
17 | Duration: parsed, |
18 | } |
19 | + maybe(db.With(func(tx *sql.Tx) error { |
20 | + id, err := db.CreateTask(tx, *task) |
21 | + if err != nil { |
22 | + return err |
23 | + } |
24 | + task.ID = id |
25 | + return nil |
26 | + })) |
27 | runner, err := NewTaskRunner(task, db, NewXnotifier(*path+"/icon.png")) |
28 | maybe(err) |
29 | server, err := NewServer(*path+"/pomo.sock", runner) |
30 | @@ -63,39 +73,51 @@ func create(path *string) func(*cli.Cmd) { |
31 | NPomodoros: *pomodoros, |
32 | Duration: parsed, |
33 | } |
34 | - taskID, err := db.CreateTask(*task) |
35 | - maybe(err) |
36 | + maybe(db.With(func(tx *sql.Tx) error { |
37 | + taskId, err := db.CreateTask(tx, *task) |
38 | + if err != nil { |
39 | + return err |
40 | + } |
41 | + fmt.Println(taskId) |
42 | + return nil |
43 | + })) |
44 | } |
45 | } |
46 | } |
47 | |
48 | func begin(path *string) func(*cli.Cmd) { |
49 | return func(cmd *cli.Cmd) { |
50 | - cmd.Spec = "ID" |
51 | - var jobId = cmd.IntArg("ID", -1, "ID of Pomodoro to begin") |
52 | + cmd.Spec = "[OPTIONS] TASK_ID" |
53 | + var ( |
54 | + taskId = cmd.IntArg("TASK_ID", -1, "ID of Pomodoro to begin") |
55 | + ) |
56 | |
57 | cmd.Action = func() { |
58 | db, err := NewStore(*path) |
59 | maybe(err) |
60 | defer db.Close() |
61 | - tasks, err := db.ReadTasks() |
62 | - maybe(err) |
63 | - task := &Task{} |
64 | - for _, task = range tasks { |
65 | - if task.ID == *jobId { |
66 | - break |
67 | + var task *Task |
68 | + maybe(db.With(func(tx *sql.Tx) error { |
69 | + read, err := db.ReadTask(tx, *taskId) |
70 | + if err != nil { |
71 | + return err |
72 | } |
73 | - } |
74 | - if task.ID == *jobId { |
75 | - runner, err := NewTaskRunner(task, db, NewXnotifier(*path+"/icon.png")) |
76 | - maybe(err) |
77 | - server, err := NewServer(*path+"/pomo.sock", runner) |
78 | - maybe(err) |
79 | - server.Start() |
80 | - defer server.Stop() |
81 | - runner.Start() |
82 | - startUI(runner) |
83 | - } |
84 | + task = read |
85 | + err = db.DeletePomodoros(tx, *taskId) |
86 | + if err != nil { |
87 | + return err |
88 | + } |
89 | + task.Pomodoros = []*Pomodoro{} |
90 | + return nil |
91 | + })) |
92 | + runner, err := NewTaskRunner(task, db, NewXnotifier(*path+"/icon.png")) |
93 | + maybe(err) |
94 | + server, err := NewServer(*path+"/pomo.sock", runner) |
95 | + maybe(err) |
96 | + server.Start() |
97 | + defer server.Stop() |
98 | + runner.Start() |
99 | + startUI(runner) |
100 | } |
101 | } |
102 | } |
103 | @@ -128,24 +150,27 @@ func list(path *string) func(*cli.Cmd) { |
104 | db, err := NewStore(*path) |
105 | maybe(err) |
106 | defer db.Close() |
107 | - tasks, err := db.ReadTasks() |
108 | - maybe(err) |
109 | - if *assend { |
110 | - sort.Sort(sort.Reverse(ByID(tasks))) |
111 | - } |
112 | - if !*all { |
113 | - tasks = After(time.Now().Add(-duration), tasks) |
114 | - } |
115 | - if *limit > 0 && (len(tasks) > *limit) { |
116 | - tasks = tasks[0:*limit] |
117 | - } |
118 | - if *asJSON { |
119 | - maybe(json.NewEncoder(os.Stdout).Encode(tasks)) |
120 | - return |
121 | - } |
122 | - config, err := NewConfig(*path + "/config.json") |
123 | - maybe(err) |
124 | - summerizeTasks(config, tasks) |
125 | + maybe(db.With(func(tx *sql.Tx) error { |
126 | + tasks, err := db.ReadTasks(tx) |
127 | + maybe(err) |
128 | + if *assend { |
129 | + sort.Sort(sort.Reverse(ByID(tasks))) |
130 | + } |
131 | + if !*all { |
132 | + tasks = After(time.Now().Add(-duration), tasks) |
133 | + } |
134 | + if *limit > 0 && (len(tasks) > *limit) { |
135 | + tasks = tasks[0:*limit] |
136 | + } |
137 | + if *asJSON { |
138 | + maybe(json.NewEncoder(os.Stdout).Encode(tasks)) |
139 | + return nil |
140 | + } |
141 | + config, err := NewConfig(*path + "/config.json") |
142 | + maybe(err) |
143 | + summerizeTasks(config, tasks) |
144 | + return nil |
145 | + })) |
146 | } |
147 | } |
148 | } |
149 | @@ -158,7 +183,9 @@ func _delete(path *string) func(*cli.Cmd) { |
150 | db, err := NewStore(*path) |
151 | maybe(err) |
152 | defer db.Close() |
153 | - maybe(db.DeleteTask(*taskID)) |
154 | + maybe(db.With(func(tx *sql.Tx) error { |
155 | + return db.DeleteTask(tx, *taskID) |
156 | + })) |
157 | } |
158 | } |
159 | } |
160 | diff --git a/runner.go b/runner.go |
161 | index 9823484..97869d6 100644 |
162 | --- a/runner.go |
163 | +++ b/runner.go |
164 | @@ -1,6 +1,7 @@ |
165 | package main |
166 | |
167 | import ( |
168 | + "database/sql" |
169 | "time" |
170 | ) |
171 | |
172 | @@ -20,12 +21,8 @@ type TaskRunner struct { |
173 | } |
174 | |
175 | func NewTaskRunner(task *Task, store *Store, notifier Notifier) (*TaskRunner, error) { |
176 | - taskID, err := store.CreateTask(*task) |
177 | - if err != nil { |
178 | - return nil, err |
179 | - } |
180 | tr := &TaskRunner{ |
181 | - taskID: taskID, |
182 | + taskID: task.ID, |
183 | taskMessage: task.Message, |
184 | nPomodoros: task.NPomodoros, |
185 | origDuration: task.Duration, |
186 | @@ -89,7 +86,9 @@ func (t *TaskRunner) run() error { |
187 | goto loop |
188 | } |
189 | pomodoro.End = time.Now() |
190 | - err := t.store.CreatePomodoro(t.taskID, *pomodoro) |
191 | + err := t.store.With(func(tx *sql.Tx) error { |
192 | + return t.store.CreatePomodoro(tx, t.taskID, *pomodoro) |
193 | + }) |
194 | if err != nil { |
195 | return err |
196 | } |
197 | diff --git a/store.go b/store.go |
198 | index a56ed75..0152fbe 100644 |
199 | --- a/store.go |
200 | +++ b/store.go |
201 | @@ -12,6 +12,8 @@ import ( |
202 | // 2018-01-16 19:05:21.752851759+08:00 |
203 | const datetimeFmt = "2006-01-02 15:04:05.999999999-07:00" |
204 | |
205 | + type StoreFunc func(tx *sql.Tx) error |
206 | + |
207 | type Store struct { |
208 | db *sql.DB |
209 | } |
210 | @@ -25,39 +27,45 @@ func NewStore(path string) (*Store, error) { |
211 | return &Store{db: db}, nil |
212 | } |
213 | |
214 | - func (s Store) CreateTask(task Task) (int, error) { |
215 | - var taskID int |
216 | + // With applies all of the given functions with |
217 | + // a single transaction, rolling back on failure |
218 | + // and commiting on success. |
219 | + func (s Store) With(fns ...func(tx *sql.Tx) error) error { |
220 | tx, err := s.db.Begin() |
221 | if err != nil { |
222 | - return -1, err |
223 | + return err |
224 | } |
225 | - _, err = tx.Exec( |
226 | + for _, fn := range fns { |
227 | + err = fn(tx) |
228 | + if err != nil { |
229 | + tx.Rollback() |
230 | + return err |
231 | + } |
232 | + } |
233 | + return tx.Commit() |
234 | + } |
235 | + |
236 | + func (s Store) CreateTask(tx *sql.Tx, task Task) (int, error) { |
237 | + var taskID int |
238 | + _, err := tx.Exec( |
239 | "INSERT INTO task (message,pomodoros,duration,tags) VALUES ($1,$2,$3,$4)", |
240 | task.Message, task.NPomodoros, task.Duration.String(), strings.Join(task.Tags, ",")) |
241 | if err != nil { |
242 | - tx.Rollback() |
243 | return -1, err |
244 | } |
245 | err = tx.QueryRow("SELECT last_insert_rowid() FROM task").Scan(&taskID) |
246 | if err != nil { |
247 | - tx.Rollback() |
248 | return -1, err |
249 | } |
250 | - return taskID, tx.Commit() |
251 | - } |
252 | - |
253 | - func (s Store) CreatePomodoro(taskID int, pomodoro Pomodoro) error { |
254 | - _, err := s.db.Exec( |
255 | - `INSERT INTO pomodoro (task_id, start, end) VALUES ($1, $2, $3)`, |
256 | - taskID, |
257 | - pomodoro.Start, |
258 | - pomodoro.End, |
259 | - ) |
260 | - return err |
261 | + err = tx.QueryRow("SELECT last_insert_rowid() FROM task").Scan(&taskID) |
262 | + if err != nil { |
263 | + return -1, err |
264 | + } |
265 | + return taskID, nil |
266 | } |
267 | |
268 | - func (s Store) ReadTasks() ([]*Task, error) { |
269 | - rows, err := s.db.Query(`SELECT rowid,message,pomodoros,duration,tags FROM task`) |
270 | + func (s Store) ReadTasks(tx *sql.Tx) ([]*Task, error) { |
271 | + rows, err := tx.Query(`SELECT rowid,message,pomodoros,duration,tags FROM task`) |
272 | if err != nil { |
273 | return nil, err |
274 | } |
275 | @@ -77,7 +85,7 @@ func (s Store) ReadTasks() ([]*Task, error) { |
276 | if tags != "" { |
277 | task.Tags = strings.Split(tags, ",") |
278 | } |
279 | - pomodoros, err := s.ReadPomodoros(task.ID) |
280 | + pomodoros, err := s.ReadPomodoros(tx, task.ID) |
281 | if err != nil { |
282 | return nil, err |
283 | } |
284 | @@ -89,8 +97,49 @@ func (s Store) ReadTasks() ([]*Task, error) { |
285 | return tasks, nil |
286 | } |
287 | |
288 | - func (s Store) ReadPomodoros(taskID int) ([]*Pomodoro, error) { |
289 | - rows, err := s.db.Query(`SELECT start,end FROM pomodoro WHERE task_id = $1`, &taskID) |
290 | + func (s Store) DeleteTask(tx *sql.Tx, taskID int) error { |
291 | + _, err := tx.Exec("DELETE FROM task WHERE rowid = $1", &taskID) |
292 | + if err != nil { |
293 | + return err |
294 | + } |
295 | + _, err = tx.Exec("DELETE FROM pomodoro WHERE task_id = $1", &taskID) |
296 | + if err != nil { |
297 | + return err |
298 | + } |
299 | + return nil |
300 | + } |
301 | + |
302 | + func (s Store) ReadTask(tx *sql.Tx, taskID int) (*Task, error) { |
303 | + task := &Task{} |
304 | + var ( |
305 | + tags string |
306 | + strDuration string |
307 | + ) |
308 | + err := tx.QueryRow(`SELECT rowid,message,pomodoros,duration,tags FROM task WHERE rowid = $1`, &taskID). |
309 | + Scan(&task.ID, &task.Message, &task.NPomodoros, &strDuration, &tags) |
310 | + if err != nil { |
311 | + return nil, err |
312 | + } |
313 | + duration, _ := time.ParseDuration(strDuration) |
314 | + task.Duration = duration |
315 | + if tags != "" { |
316 | + task.Tags = strings.Split(tags, ",") |
317 | + } |
318 | + return task, nil |
319 | + } |
320 | + |
321 | + func (s Store) CreatePomodoro(tx *sql.Tx, taskID int, pomodoro Pomodoro) error { |
322 | + _, err := tx.Exec( |
323 | + `INSERT INTO pomodoro (task_id, start, end) VALUES ($1, $2, $3)`, |
324 | + taskID, |
325 | + pomodoro.Start, |
326 | + pomodoro.End, |
327 | + ) |
328 | + return err |
329 | + } |
330 | + |
331 | + func (s Store) ReadPomodoros(tx *sql.Tx, taskID int) ([]*Pomodoro, error) { |
332 | + rows, err := tx.Query(`SELECT start,end FROM pomodoro WHERE task_id = $1`, &taskID) |
333 | if err != nil { |
334 | return nil, err |
335 | } |
336 | @@ -114,22 +163,9 @@ func (s Store) ReadPomodoros(taskID int) ([]*Pomodoro, error) { |
337 | return pomodoros, nil |
338 | } |
339 | |
340 | - func (s Store) DeleteTask(taskID int) error { |
341 | - tx, err := s.db.Begin() |
342 | - if err != nil { |
343 | - return err |
344 | - } |
345 | - _, err = tx.Exec("DELETE FROM task WHERE rowid = $1", &taskID) |
346 | - if err != nil { |
347 | - tx.Rollback() |
348 | - return err |
349 | - } |
350 | - _, err = tx.Exec("DELETE FROM pomodoro WHERE task_id = $1", &taskID) |
351 | - if err != nil { |
352 | - tx.Rollback() |
353 | - return err |
354 | - } |
355 | - return tx.Commit() |
356 | + func (s Store) DeletePomodoros(tx *sql.Tx, taskID int) error { |
357 | + _, err := tx.Exec("DELETE FROM pomodoro WHERE task_id = $1", &taskID) |
358 | + return err |
359 | } |
360 | |
361 | func (s Store) Close() error { return s.db.Close() } |