avoid removal of recurrent task due to TW-638 bug of taskwarrior
[ptask.git] / src / tw.c
1 /*
2  * Copyright (C) 2012-2013 jeanfi@gmail.com
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301 USA
18  */
19 #define _GNU_SOURCE
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <time.h>
26
27 #include <json.h>
28
29 #include <log.h>
30 #include "note.h"
31 #include <pstr.h>
32 #include "tw.h"
33
34 struct tm *parse_time(const char *t)
35 {
36         struct tm *tm;
37
38         tm = malloc(sizeof(struct tm));
39         memset(tm, 0, sizeof(struct tm));
40         strptime(t, "%Y%m%dT%H%M%S%Z", tm);
41
42         return tm;
43 }
44
45 static char *task_exec(char *opts)
46 {
47         FILE *f;
48         int ret;
49         size_t s;
50         char *str, *tmp, *cmd, buf[1024];
51
52         log_fct_enter();
53
54         cmd = malloc(strlen("task ") + strlen(opts) + 1);
55         strcpy(cmd, "task ");
56         strcat(cmd, opts);
57
58         log_debug("execute: %s", cmd);
59
60         f = popen(cmd, "r");
61
62         free(cmd);
63
64         if (!f) {
65                 perror("popen");
66                 return NULL;
67         }
68
69         str = strdup("");
70         while ((s = fread(buf, 1, 1024, f))) {
71                 tmp = malloc(strlen(str) + s + (size_t)1);
72                 memcpy(tmp, str, strlen(str));
73                 memcpy(tmp + strlen(str), buf, s);
74                 tmp[strlen(str) + s] = '\0';
75                 free(str);
76                 str = tmp;
77         }
78
79         ret = pclose(f);
80
81         if (ret == -1)
82                 log_err("pclose fails");
83
84         log_fct_exit();
85
86         return str;
87 }
88
89 static char *task_get_version()
90 {
91         char *out;
92
93         out = task_exec("--version");
94
95         trim(out);
96
97         return out;
98 }
99
100 static int task_check_version()
101 {
102         char *ver;
103
104         ver = task_get_version();
105
106         if (!ver)
107                 return 0;
108
109         log_debug("task version: %s", ver);
110
111         if (!strcmp(ver, "2.2.0")
112             || !strcmp(ver, "2.0.0")
113             || !strcmp(ver, "2.3.0"))
114                 return 1;
115         else
116                 return 0;
117 }
118
119 static char *tw_exec(char *opts)
120 {
121         char *opts2;
122
123         if (!task_check_version()) {
124                 log_err("ptask is not compatible with the installed version of"
125                         " taskwarrior.");
126                 return NULL;
127         }
128
129         opts2 = malloc(strlen("rc.confirmation:no ")
130                        + strlen(opts)
131                        + 1);
132         strcpy(opts2, "rc.confirmation:no ");
133         strcat(opts2, opts);
134
135         return task_exec(opts2);
136 }
137
138 static struct json_object *task_exec_json(const char *opts)
139 {
140         struct json_object *o;
141         char *str, *cmd;
142
143         cmd = malloc(strlen("rc.json.array=on ") + strlen(opts) + 1);
144         strcpy(cmd, "rc.json.array=on ");
145         strcat(cmd, opts);
146
147         str = tw_exec(cmd);
148
149         if (str) {
150                 o = json_tokener_parse(str);
151                 free(str);
152         } else {
153                 o = NULL;
154         }
155
156         free(cmd);
157
158         if (o && is_error(o))
159                 return NULL;
160
161         return o;
162 }
163
164 struct task **tw_get_all_tasks(const char *status)
165 {
166         int i, n;
167         struct json_object *jtasks, *jtask, *json;
168         struct task **tasks;
169         char *opts;
170         const char *urg;
171
172         opts = malloc(strlen("export status:") + strlen(status) + 1);
173
174         strcpy(opts, "export status:");
175         strcat(opts, status);
176
177         jtasks = task_exec_json(opts);
178         free(opts);
179
180         if (!jtasks)
181                 return NULL;
182
183         n = json_object_array_length(jtasks);
184
185         tasks = malloc((n + 1) * sizeof(struct task *));
186
187         for (i = 0; i < n; i++) {
188                 jtask = json_object_array_get_idx(jtasks, i);
189
190                 tasks[i] = malloc(sizeof(struct task));
191
192                 json = json_object_object_get(jtask, "id");
193                 tasks[i]->id = json_object_get_int(json);
194
195                 json = json_object_object_get(jtask, "description");
196                 tasks[i]->description = strdup(json_object_get_string(json));
197
198                 json = json_object_object_get(jtask, "status");
199                 tasks[i]->status = strdup(json_object_get_string(json));
200
201                 json = json_object_object_get(jtask, "project");
202                 if (json)
203                         tasks[i]->project
204                                 = strdup(json_object_get_string(json));
205                 else
206                         tasks[i]->project = strdup("");
207
208                 json = json_object_object_get(jtask, "priority");
209                 if (json)
210                         tasks[i]->priority
211                                 = strdup(json_object_get_string(json));
212                 else
213                         tasks[i]->priority = strdup("");
214
215                 json = json_object_object_get(jtask, "uuid");
216                 tasks[i]->uuid = strdup(json_object_get_string(json));
217
218                 json = json_object_object_get(jtask, "urgency");
219                 urg = json_object_get_string(json);
220                 if (urg)
221                         tasks[i]->urgency = strdup(urg);
222                 else
223                         tasks[i]->urgency = NULL;
224
225                 tasks[i]->note = note_get(tasks[i]->uuid);
226
227                 json = json_object_object_get(jtask, "entry");
228                 tasks[i]->entry = parse_time(json_object_get_string(json));
229
230                 json = json_object_object_get(jtask, "due");
231                 if (json)
232                         tasks[i]->due
233                                 = parse_time(json_object_get_string(json));
234                 else
235                         tasks[i]->due = NULL;
236
237                 json = json_object_object_get(jtask, "start");
238                 if (json)
239                         tasks[i]->start
240                                 = parse_time(json_object_get_string(json));
241                 else
242                         tasks[i]->start = NULL;
243
244                 json = json_object_object_get(jtask, "recur");
245                 if (json)
246                         tasks[i]->recur = strdup(json_object_get_string(json));
247                 else
248                         tasks[i]->recur = NULL;
249         }
250
251         tasks[n] = NULL;
252
253         json_object_put(jtasks);
254
255         return tasks;
256 }
257
258 static char *escape(const char *txt)
259 {
260         char *result;
261         char *c;
262
263         result = malloc(2*strlen(txt)+1);
264         c = result;
265
266         while (*txt) {
267                 switch (*txt) {
268                 case '"':
269                 case '$':
270                 case '&':
271                 case '<':
272                 case '>':
273                         *c = '\\'; c++;
274                         *c = *txt;
275                         break;
276                 default:
277                         *c = *txt;
278                 }
279                 c++;
280                 txt++;
281         }
282
283         *c = '\0';
284
285         return result;
286 }
287
288 void tw_modify_description(const char *uuid, const char *newdesc)
289 {
290         char *opts;
291
292         opts = malloc(1
293                       + strlen(uuid)
294                       + strlen(" modify :\"")
295                       + strlen(newdesc)
296                       + strlen("\"")
297                       + 1);
298         sprintf(opts, " %s modify \"%s\"", uuid, newdesc);
299
300         tw_exec(opts);
301
302         free(opts);
303 }
304
305 void tw_modify_project(const char *uuid, const char *newproject)
306 {
307         char *str;
308         char *opts;
309
310         str = escape(newproject);
311
312         opts = malloc(1
313                       + strlen(uuid)
314                       + strlen(" modify project:\"")
315                       + strlen(str)
316                       + strlen("\"")
317                       + 1);
318         sprintf(opts, " %s modify project:\"%s\"", uuid, str);
319
320         tw_exec(opts);
321
322         free(str);
323         free(opts);
324 }
325
326 void tw_modify_priority(const char *uuid, const char *priority)
327 {
328         char *str;
329         char *opts;
330
331         log_fct_enter();
332
333         str = escape(priority);
334
335         opts = malloc(1
336                       + strlen(uuid)
337                       + strlen(" modify priority:\"")
338                       + strlen(str)
339                       + strlen("\"")
340                       + 1);
341         sprintf(opts, " %s modify priority:\"%s\"", uuid, str);
342
343         tw_exec(opts);
344
345         free(str);
346         free(opts);
347
348         log_fct_exit();
349 }
350
351 void tw_add(const char *newdesc, const char *prj, const char *prio)
352 {
353         char *opts, *eprj;
354
355         log_fct_enter();
356
357         eprj = escape(prj);
358
359         opts = malloc(strlen("add")
360                       + strlen(" priority:")
361                       + 1
362                       + strlen(" project:\\\"")
363                       + strlen(eprj)
364                       + strlen("\\\"")
365                       + strlen(" \"")
366                       + strlen(newdesc)
367                       + strlen("\"")
368                       + 1);
369
370         strcpy(opts, "add");
371
372         if (prio && strlen(prio) == 1) {
373                 strcat(opts, " priority:");
374                 strcat(opts, prio);
375         }
376
377         if (eprj && strlen(prj)) {
378                 strcat(opts, " project:\\\"");
379                 strcat(opts, eprj);
380                 strcat(opts, "\\\"");
381         }
382
383         strcat(opts, " \"");
384         strcat(opts, newdesc);
385         strcat(opts, "\"");
386
387         tw_exec(opts);
388
389         free(opts);
390         free(eprj);
391
392         log_fct_exit();
393 }
394
395 void tw_task_done(const char *uuid)
396 {
397         char *opts;
398
399         opts = malloc(1
400                       + strlen(uuid)
401                       + strlen(" done")
402                       + 1);
403         sprintf(opts, " %s done", uuid);
404
405         tw_exec(opts);
406
407         free(opts);
408 }
409
410 void tw_task_start(const char *uuid)
411 {
412         char *opts;
413
414         opts = malloc(1
415                       + strlen(uuid)
416                       + strlen(" start")
417                       + 1);
418         sprintf(opts, " %s start", uuid);
419
420         tw_exec(opts);
421
422         free(opts);
423 }
424
425 void tw_task_stop(const char *uuid)
426 {
427         char *opts;
428
429         opts = malloc(1
430                       + strlen(uuid)
431                       + strlen(" stop")
432                       + 1);
433         sprintf(opts, " %s stop", uuid);
434
435         tw_exec(opts);
436
437         free(opts);
438 }
439
440 void tw_task_remove(const char *uuid)
441 {
442         char *opts;
443
444         opts = malloc(1
445                       + strlen(uuid)
446                       + strlen(" delete")
447                       + 1);
448         sprintf(opts, " %s delete", uuid);
449
450         tw_exec(opts);
451
452         free(opts);
453 }
454
455 static void task_free(struct task *task)
456 {
457         if (!task)
458                 return ;
459
460         free(task->description);
461         free(task->status);
462         free(task->uuid);
463         free(task->note);
464         free(task->project);
465         free(task->priority);
466         free(task->urgency);
467         free(task->entry);
468         free(task->due);
469         free(task->start);
470         free(task->recur);
471
472         free(task);
473 }
474
475 void tw_task_list_free(struct task **tasks)
476 {
477         struct task **cur;
478
479         if (!tasks)
480                 return ;
481
482         for (cur = tasks; *cur; cur++)
483                 task_free(*cur);
484
485         free(tasks);
486 }
487
488 static void project_free(struct project *p)
489 {
490         if (!p)
491                 return ;
492
493         free(p->name);
494         free(p);
495 }
496
497 void tw_project_list_free(struct project **prjs)
498 {
499         struct project **cur;
500
501         if (!prjs)
502                 return ;
503
504         for (cur = prjs; *cur; cur++)
505                 project_free(*cur);
506
507         free(prjs);
508 }
509
510 static struct project *project_list_get(struct project **prjs, const char *name)
511 {
512         struct project **cur;
513
514         for (cur = prjs; *cur; cur++)
515                 if (!strcmp((*cur)->name, name))
516                         return *cur;
517
518         return NULL;
519 }
520
521 static struct project *project_new(const char *name, int count)
522 {
523         struct project *prj;
524
525         prj = malloc(sizeof(struct project));
526
527         prj->name = strdup(name);
528         prj->count = count;
529
530         return prj;
531 }
532
533 static int projects_length(struct project **list)
534 {
535         int n;
536
537         if (!list)
538                 return 0;
539
540         n = 0;
541         while (*list) {
542                 n++;
543                 list++;
544         }
545
546         return n;
547 }
548
549 static struct project **projects_add(struct project **list, void *item)
550 {
551         int n;
552         struct project **result;
553
554         n = projects_length(list);
555
556         result = (struct project **)malloc
557                 ((n + 1 + 1) * sizeof(struct project *));
558
559         if (list)
560                 memcpy(result, list, n * sizeof(struct project *));
561
562         result[n] = item;
563         result[n + 1] = NULL;
564
565         return result;
566 }
567
568 struct project **tw_get_projects(struct task **tasks)
569 {
570         struct task **t_cur;
571         struct project **prjs, **tmp, *prj;
572         const char *prj_name;
573
574         log_fct_enter();
575
576         prjs = malloc(2 * sizeof(struct project *));
577         prjs[0] = project_new("ALL", 0);
578         prjs[1] = NULL;
579
580         for (t_cur = tasks; *t_cur; t_cur++) {
581                 prj_name = (*t_cur)->project;
582                 prj = project_list_get(prjs, prj_name);
583                 if (prj) {
584                         prj->count++;
585                 } else {
586                         prj = project_new(prj_name, 1);
587
588                         tmp = projects_add(prjs, prj);
589
590                         free(prjs);
591                         prjs = tmp;
592                 }
593                 prjs[0]->count++;
594         }
595
596         log_fct_exit();
597
598         return prjs;
599 }