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