add taskwarrior 2.4.1 support
[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             || !strcmp(ver, "2.4.0")
115             || !strcmp(ver, "2.4.1"))
116                 return 1;
117         else
118                 return 0;
119 }
120
121 static char *tw_exec(char *opts)
122 {
123         char *opts2;
124
125         if (!task_check_version()) {
126                 log_err("ptask is not compatible with the installed version of"
127                         " taskwarrior.");
128                 return NULL;
129         }
130
131         opts2 = malloc(strlen("rc.confirmation:no ")
132                        + strlen(opts)
133                        + 1);
134         strcpy(opts2, "rc.confirmation:no ");
135         strcat(opts2, opts);
136
137         return task_exec(opts2);
138 }
139
140 static struct json_object *task_exec_json(const char *opts)
141 {
142         struct json_object *o;
143         char *str, *cmd;
144
145         cmd = malloc(strlen("rc.json.array=on ") + strlen(opts) + 1);
146         strcpy(cmd, "rc.json.array=on ");
147         strcat(cmd, opts);
148
149         str = tw_exec(cmd);
150
151         if (str) {
152                 o = json_tokener_parse(str);
153                 free(str);
154         } else {
155                 o = NULL;
156         }
157
158         free(cmd);
159
160         if (o && is_error(o))
161                 return NULL;
162
163         return o;
164 }
165
166 char **json_to_tags(struct json_object *jtask)
167 {
168         struct json_object *jtags, *jtag;
169         char **tags;
170         int n, i;
171
172         jtags = json_object_object_get(jtask, "tags");
173
174         if (!jtags)
175                 return NULL;
176
177
178         n = json_object_array_length(jtags);
179
180         tags = malloc((n + 1) * sizeof(char *));
181
182         for (i = 0; i < n; i++) {
183                 jtag = json_object_array_get_idx(jtags, i);
184                 tags[i] = strdup(json_object_get_string(jtag));
185         }
186
187         tags[n] = NULL;
188
189         return tags;
190 }
191
192 struct task *json_to_task(struct json_object *jtask)
193 {
194         struct task *task;
195         const char *urg;
196         struct json_object *json;
197
198         task = malloc(sizeof(struct task));
199
200         json = json_object_object_get(jtask, "id");
201         task->id = json_object_get_int(json);
202
203         json = json_object_object_get(jtask, "description");
204         task->description = strdup(json_object_get_string(json));
205
206         json = json_object_object_get(jtask, "status");
207         task->status = strdup(json_object_get_string(json));
208
209         json = json_object_object_get(jtask, "project");
210         if (json)
211                 task->project
212                         = strdup(json_object_get_string(json));
213         else
214                 task->project = strdup("");
215
216         json = json_object_object_get(jtask, "priority");
217         if (json)
218                 task->priority
219                         = strdup(json_object_get_string(json));
220         else
221                 task->priority = strdup("");
222
223         json = json_object_object_get(jtask, "uuid");
224         task->uuid = strdup(json_object_get_string(json));
225
226         json = json_object_object_get(jtask, "urgency");
227         urg = json_object_get_string(json);
228         if (urg)
229                 task->urgency = strdup(urg);
230         else
231                 task->urgency = NULL;
232
233         task->note = note_get(task->uuid);
234
235         json = json_object_object_get(jtask, "entry");
236         task->entry = parse_time(json_object_get_string(json));
237
238         json = json_object_object_get(jtask, "due");
239         if (json)
240                 task->due
241                         = parse_time(json_object_get_string(json));
242         else
243                 task->due = NULL;
244
245         json = json_object_object_get(jtask, "start");
246         if (json)
247                 task->start
248                         = parse_time(json_object_get_string(json));
249         else
250                 task->start = NULL;
251
252         json = json_object_object_get(jtask, "recur");
253         if (json)
254                 task->recur = strdup(json_object_get_string(json));
255         else
256                 task->recur = NULL;
257
258         task->tags = json_to_tags(jtask);
259
260         return task;
261 }
262
263 struct task **tw_get_all_tasks(const char *status)
264 {
265         int i, n;
266         struct json_object *jtasks, *jtask;
267         struct task **tasks;
268         char *opts;
269
270         opts = malloc(strlen("export status:") + strlen(status) + 1);
271
272         strcpy(opts, "export status:");
273         strcat(opts, status);
274
275         jtasks = task_exec_json(opts);
276         free(opts);
277
278         if (!jtasks)
279                 return NULL;
280
281         n = json_object_array_length(jtasks);
282
283         tasks = malloc((n + 1) * sizeof(struct task *));
284
285         for (i = 0; i < n; i++) {
286                 jtask = json_object_array_get_idx(jtasks, i);
287
288                 tasks[i] = json_to_task(jtask);
289         }
290
291         tasks[n] = NULL;
292
293         json_object_put(jtasks);
294
295         return tasks;
296 }
297
298 static char *escape(const char *txt)
299 {
300         char *result;
301         char *c;
302
303         result = malloc(2*strlen(txt)+1);
304         c = result;
305
306         while (*txt) {
307                 switch (*txt) {
308                 case '"':
309                 case '$':
310                 case '&':
311                 case '<':
312                 case '>':
313                         *c = '\\'; c++;
314                         *c = *txt;
315                         break;
316                 default:
317                         *c = *txt;
318                 }
319                 c++;
320                 txt++;
321         }
322
323         *c = '\0';
324
325         return result;
326 }
327
328 void tw_modify_description(const char *uuid, const char *newdesc)
329 {
330         char *opts;
331
332         opts = malloc(1
333                       + strlen(uuid)
334                       + strlen(" modify :\"")
335                       + strlen(newdesc)
336                       + strlen("\"")
337                       + 1);
338         sprintf(opts, " %s modify \"%s\"", uuid, newdesc);
339
340         tw_exec(opts);
341
342         free(opts);
343 }
344
345 void tw_modify_project(const char *uuid, const char *newproject)
346 {
347         char *str;
348         char *opts;
349
350         str = escape(newproject);
351
352         opts = malloc(1
353                       + strlen(uuid)
354                       + strlen(" modify project:\"")
355                       + strlen(str)
356                       + strlen("\"")
357                       + 1);
358         sprintf(opts, " %s modify project:\"%s\"", uuid, str);
359
360         tw_exec(opts);
361
362         free(str);
363         free(opts);
364 }
365
366 void tw_modify_priority(const char *uuid, const char *priority)
367 {
368         char *str;
369         char *opts;
370
371         log_fct_enter();
372
373         str = escape(priority);
374
375         opts = malloc(1
376                       + strlen(uuid)
377                       + strlen(" modify priority:\"")
378                       + strlen(str)
379                       + strlen("\"")
380                       + 1);
381         sprintf(opts, " %s modify priority:\"%s\"", uuid, str);
382
383         tw_exec(opts);
384
385         free(str);
386         free(opts);
387
388         log_fct_exit();
389 }
390
391 void tw_add(const char *newdesc, const char *prj, const char *prio)
392 {
393         char *opts, *eprj;
394
395         log_fct_enter();
396
397         eprj = escape(prj);
398
399         opts = malloc(strlen("add")
400                       + strlen(" priority:")
401                       + 1
402                       + strlen(" project:\\\"")
403                       + strlen(eprj)
404                       + strlen("\\\"")
405                       + strlen(" \"")
406                       + strlen(newdesc)
407                       + strlen("\"")
408                       + 1);
409
410         strcpy(opts, "add");
411
412         if (prio && strlen(prio) == 1) {
413                 strcat(opts, " priority:");
414                 strcat(opts, prio);
415         }
416
417         if (eprj && strlen(prj)) {
418                 strcat(opts, " project:\\\"");
419                 strcat(opts, eprj);
420                 strcat(opts, "\\\"");
421         }
422
423         strcat(opts, " \"");
424         strcat(opts, newdesc);
425         strcat(opts, "\"");
426
427         tw_exec(opts);
428
429         free(opts);
430         free(eprj);
431
432         log_fct_exit();
433 }
434
435 void tw_task_done(const char *uuid)
436 {
437         char *opts;
438
439         opts = malloc(1
440                       + strlen(uuid)
441                       + strlen(" done")
442                       + 1);
443         sprintf(opts, " %s done", uuid);
444
445         tw_exec(opts);
446
447         free(opts);
448 }
449
450 void tw_task_start(const char *uuid)
451 {
452         char *opts;
453
454         opts = malloc(1
455                       + strlen(uuid)
456                       + strlen(" start")
457                       + 1);
458         sprintf(opts, " %s start", uuid);
459
460         tw_exec(opts);
461
462         free(opts);
463 }
464
465 void tw_task_stop(const char *uuid)
466 {
467         char *opts;
468
469         opts = malloc(1
470                       + strlen(uuid)
471                       + strlen(" stop")
472                       + 1);
473         sprintf(opts, " %s stop", uuid);
474
475         tw_exec(opts);
476
477         free(opts);
478 }
479
480 void tw_task_remove(const char *uuid)
481 {
482         char *opts;
483
484         opts = malloc(1
485                       + strlen(uuid)
486                       + strlen(" delete")
487                       + 1);
488         sprintf(opts, " %s delete", uuid);
489
490         tw_exec(opts);
491
492         free(opts);
493 }
494
495 static void task_free(struct task *task)
496 {
497         char **tags;
498
499         if (!task)
500                 return ;
501
502         free(task->description);
503         free(task->status);
504         free(task->uuid);
505         free(task->note);
506         free(task->project);
507         free(task->priority);
508         free(task->urgency);
509         free(task->entry);
510         free(task->due);
511         free(task->start);
512         free(task->recur);
513
514         tags = task->tags;
515         if (tags) {
516                 while (*tags) {
517                         free(*tags);
518                         tags++;
519                 }
520                 free(task->tags);
521         }
522
523         free(task);
524 }
525
526 void tw_task_list_free(struct task **tasks)
527 {
528         struct task **cur;
529
530         if (!tasks)
531                 return ;
532
533         for (cur = tasks; *cur; cur++)
534                 task_free(*cur);
535
536         free(tasks);
537 }
538
539 static void project_free(struct project *p)
540 {
541         if (!p)
542                 return ;
543
544         free(p->name);
545         free(p);
546 }
547
548 void tw_project_list_free(struct project **prjs)
549 {
550         struct project **cur;
551
552         if (!prjs)
553                 return ;
554
555         for (cur = prjs; *cur; cur++)
556                 project_free(*cur);
557
558         free(prjs);
559 }
560
561 static struct project *project_list_get(struct project **prjs, const char *name)
562 {
563         struct project **cur;
564
565         for (cur = prjs; *cur; cur++)
566                 if (!strcmp((*cur)->name, name))
567                         return *cur;
568
569         return NULL;
570 }
571
572 static struct project *project_new(const char *name, int count)
573 {
574         struct project *prj;
575
576         prj = malloc(sizeof(struct project));
577
578         prj->name = strdup(name);
579         prj->count = count;
580
581         return prj;
582 }
583
584 static int projects_length(struct project **list)
585 {
586         int n;
587
588         if (!list)
589                 return 0;
590
591         n = 0;
592         while (*list) {
593                 n++;
594                 list++;
595         }
596
597         return n;
598 }
599
600 static struct project **projects_add(struct project **list, void *item)
601 {
602         int n;
603         struct project **result;
604
605         n = projects_length(list);
606
607         result = (struct project **)malloc
608                 ((n + 1 + 1) * sizeof(struct project *));
609
610         if (list)
611                 memcpy(result, list, n * sizeof(struct project *));
612
613         result[n] = item;
614         result[n + 1] = NULL;
615
616         return result;
617 }
618
619 struct project **tw_get_projects(struct task **tasks)
620 {
621         struct task **t_cur;
622         struct project **prjs, **tmp, *prj;
623         const char *prj_name;
624
625         log_fct_enter();
626
627         prjs = malloc(2 * sizeof(struct project *));
628         prjs[0] = project_new("ALL", 0);
629         prjs[1] = NULL;
630
631         for (t_cur = tasks; *t_cur; t_cur++) {
632                 prj_name = (*t_cur)->project;
633                 prj = project_list_get(prjs, prj_name);
634                 if (prj) {
635                         prj->count++;
636                 } else {
637                         prj = project_new(prj_name, 1);
638
639                         tmp = projects_add(prjs, prj);
640
641                         free(prjs);
642                         prjs = tmp;
643                 }
644                 prjs[0]->count++;
645         }
646
647         log_fct_exit();
648
649         return prjs;
650 }