]> Pileus Git - lackey/blob - cals/ical.c
8b3db546223314c97b2cd65c31911edbd4083d10
[lackey] / cals / ical.c
1 /*
2  * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU 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, see <http://www.gnu.org/licenses/>.
16  */
17
18 #define _XOPEN_SOURCE
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <wordexp.h>
23 #include <ncurses.h>
24 #include <libical/ical.h>
25
26 #include "util.h"
27 #include "conf.h"
28 #include "date.h"
29 #include "cal.h"
30 #include "form.h"
31
32 /* Local types */
33 typedef struct {
34         cal_t *cal;
35         icalcomponent *comp;
36         struct icaltimetype start;
37         struct icaltimetype end;
38 } ical_inst;
39
40 typedef struct ical_t {
41         cal_t          cal;
42         char          *location;
43         char          *username;
44         char          *password;
45         icalcomponent *comp;
46         struct ical_t *next;
47 } ical_t;
48
49 /* Static data */
50 static ical_t *calendars;
51
52 /* Form fields */
53 static form_text_t   ff_name   = TEXT('t');
54 static form_text_t   ff_desc   = TEXT('d');
55 static form_text_t   ff_loc    = TEXT('o');
56 static form_list_t   ff_cat    = LIST('g');
57 static form_date_t   ff_start  = DATE('s');
58 static form_date_t   ff_end    = DATE('e');
59 static form_number_t ff_status = NUMBER('p', .f.after="%");
60 static form_date_t   ff_due    = DATE('u');
61 static form_list_t   ff_cal    = LIST('c');
62 static form_list_t   ff_recur  = LIST('r');
63 static form_number_t ff_freq   = NUMBER(0);
64 static form_button_t ff_wdays  = BUTTONS("Su Mo Tu We Th Fr Sa");
65
66 /* Edit event form */
67 static form_t form_cal = { 1, 2, {
68         { LABEL("_Title: "),     &ff_name.f                                    },
69 } };
70
71 /* Edit event form */
72 static form_t form_event = { 11, 4, {
73         { LABEL("_Title: "),     &ff_name.f                                      },
74         { LABEL("L_ocation: "),  &ff_loc.f                                       },
75         {                                                                        },
76         { LABEL("_Start: "),     &ff_start.f, LABEL("  _End: "),      &ff_end.f  },
77         { LABEL("_Calendar: "),  &ff_cal.f,   LABEL("  Cate_gory: "), &ff_cat.f  },
78         {                                                                        },
79         { LABEL("_Repeat: "),    &ff_recur.f, LABEL("  Every: "),     &ff_freq.f },
80         {                                                                        },
81         { NULL,                  &ff_wdays.f                                     },
82         {                                                                        },
83         { LABEL("_Details: "),   &ff_desc.f                                      },
84 } };
85
86 /* Edit todo form */
87 static form_t form_todo = { 11, 4, {
88         { LABEL("_Title: "),     &ff_name.f                                      },
89         { LABEL("Com_pleted: "), &ff_status.f                                    },
90         {                                                                        },
91         { LABEL("_Start:    "),  &ff_start.f, LABEL("  D_ue Date: "), &ff_due.f  },
92         { LABEL("_Calendar: "),  &ff_cal.f,   LABEL("  Cate_gory: "), &ff_cat.f  },
93         {                                                                        },
94         { LABEL("_Repeat: "),    &ff_recur.f, LABEL("  Every: "),     &ff_freq.f },
95         {                                                                        },
96         { NULL,                  &ff_wdays.f                                     },
97         {                                                                        },
98         { LABEL("_Details: "),   &ff_desc.f                                      },
99 } };
100
101 /* Helper functions */
102 static int ical_compare(const void *_a, const void *_b)
103 {
104         const ical_inst *a = _a;
105         const ical_inst *b = _b;
106         int scomp = icaltime_compare(a->start, b->start);
107         int ecomp = icaltime_compare(a->end,   b->end);
108         return scomp != 0 ? scomp :
109                ecomp != 0 ? ecomp : 0 ;
110 }
111
112 static date_t to_date(struct icaltimetype itime)
113 {
114         return get_date(icaltime_as_timet_with_zone(itime, itime.zone));
115 }
116
117 static icaltimetype to_itime(date_t date)
118 {
119         return icaltime_from_timet_with_zone(get_stamp(date), 0, NULL);
120 }
121
122 static void add_recur(cal_t *cal,
123                 icalarray *array, icalcomponent *comp,
124                 icaltimetype start, icaltimetype end,
125                 icalcomponent_kind which)
126 {
127         icalcomponent_kind kind = icalcomponent_isa(comp);
128
129         if (kind == which) {
130                 /* Get recurrence data */
131                 struct icaltimetype cstart, cend; // Component times
132                 struct icaltimetype istart, iend; // Instance times
133                 struct icaldurationtype length;   // Duration
134
135                 icalproperty             *rrule;
136                 struct icalrecurrencetype recur;
137                 icalrecur_iterator       *iter;
138
139                 cstart = icalcomponent_get_dtstart(comp);
140                 cend   = icalcomponent_get_dtend(comp);
141                 length = icaltime_subtract(cend, cstart);
142
143                 /* Full day event */
144                 if (icaltime_is_null_time(cstart) ||
145                     which == ICAL_VTODO_COMPONENT) {
146                         icalarray_append(array, &(ical_inst){
147                                 .cal   = cal,
148                                 .comp  = comp,
149                                 .start = cstart,
150                                 .end   = cend,
151                         });
152                 }
153
154                 /* Add all recurrences */
155                 rrule = icalcomponent_get_first_property(comp, ICAL_RRULE_PROPERTY);
156
157                 /* One-time event */
158                 if (!rrule) {
159                         icalarray_append(array, &(ical_inst){
160                                 .cal   = cal,
161                                 .comp  = comp,
162                                 .start = cstart,
163                                 .end   = cend,
164                         });
165                 }
166
167                 /* Recurring events */
168                 while (rrule) {
169                         recur = icalproperty_get_rrule(rrule);
170                         iter  = icalrecur_iterator_new(recur, cstart);
171
172                         /* Add recurrence for this rrule */
173                         while (1) {
174                                 istart = iend = icalrecur_iterator_next(iter);
175                                 if (icaltime_is_null_time(istart))
176                                         break;    // no more instances
177                                 if (!icaltime_is_null_time(cend))
178                                         iend = icaltime_add(iend, length);
179
180                                 if (icaltime_compare(iend, start) <= 0)
181                                         continue; // instance ends before start time
182                                 if (icaltime_compare(istart, end) >= 0)
183                                         break;    // instance begins after stop time
184
185                                 icalarray_append(array, &(ical_inst){
186                                         .cal   = cal,
187                                         .comp  = comp,
188                                         .start = istart,
189                                         .end   = iend,
190                                 });
191                         }
192
193                         icalrecur_iterator_free(iter);
194                         rrule = icalcomponent_get_next_property(comp, ICAL_RRULE_PROPERTY);
195                 }
196         }
197
198         /* Add children */
199         icalcomponent_kind find = ICAL_ANY_COMPONENT;
200         icalcomponent *child = icalcomponent_get_first_component(comp, find);
201         while (child) {
202                 add_recur(cal, array, child, start, end, which);
203                 child = icalcomponent_get_next_component(comp, find);
204         }
205 }
206
207 static void read_icals(void)
208 {
209         for (ical_t *cal = calendars; cal; cal = cal->next) {
210                 if (!cal->location)
211                         debug("Missing location for ical '%s'", cal->cal.name);
212                 if (cal->comp)
213                         continue;
214                 wordexp_t wexp;
215                 wordexp(cal->location, &wexp, WRDE_NOCMD);
216                 icalparser *parser = icalparser_new();
217                 if (wexp.we_wordc > 1)
218                         debug("Multiple calendards are not supported '%s'", cal->location);
219                 FILE *file = fopen(wexp.we_wordv[0], "r");
220                 if (!file) {
221                         debug("Cannot open ical file '%s'", wexp.we_wordv[0]);
222                 } else {
223                         icalparser_set_gen_data(parser, file);
224                         cal->comp = icalparser_parse(parser, (void*)fgets);
225                         icalparser_free(parser);
226                 }
227                 wordfree(&wexp);
228         }
229 }
230
231 /* Event functions */
232 static event_t *to_event(ical_inst *inst)
233 {
234         icalproperty *prop = icalcomponent_get_first_property(inst->comp, ICAL_CATEGORIES_PROPERTY);
235
236         event_t *event = new0(event_t);
237         event->name  = strcopy(icalcomponent_get_summary(inst->comp));
238         event->desc  = strcopy(icalcomponent_get_description(inst->comp));
239         event->loc   = strcopy(icalcomponent_get_location(inst->comp));
240         event->cat   = icalproperty_get_value_as_string_r(prop);
241         event->start = to_date(inst->start);
242         event->end   = to_date(inst->end);
243         event->cal   = inst->cal;
244         return event;
245 }
246
247 static event_t *to_events(icalarray *array)
248 {
249         event_t  list = {};
250         event_t *tail = &list;
251         for (int i = 0; i < array->num_elements; i++) {
252                  ical_inst *inst = icalarray_element_at(array, i);
253                  tail->next = to_event(inst);
254                  tail = tail->next;
255         }
256         return list.next;
257 }
258
259 static void print_events(event_t *start)
260 {
261         for (event_t *cur = start; cur; cur = cur->next)
262                 printf("%04d-%02d-%02d %02d:%02d - %s\n",
263                         cur->start.year, cur->start.month, cur->start.day,
264                         cur->start.hour, cur->start.min,
265                         cur->name ?: cur->desc ?: "[no summary]");
266 }
267
268 /* Todo functions */
269 static todo_t *to_todo(ical_inst *inst)
270 {
271         icalproperty *cat  = icalcomponent_get_first_property(inst->comp, ICAL_CATEGORIES_PROPERTY);
272         icalproperty *perc = icalcomponent_get_first_property(inst->comp, ICAL_PERCENTCOMPLETE_PROPERTY);
273
274         todo_t *todo = new0(todo_t);
275         todo->name   = strcopy(icalcomponent_get_summary(inst->comp));
276         todo->desc   = strcopy(icalcomponent_get_description(inst->comp));
277         todo->cat    = strcopy(icalproperty_get_value_as_string(cat));
278         todo->status = icalcomponent_get_status(inst->comp) == ICAL_STATUS_COMPLETED ? 100 :
279                        perc ? icalproperty_get_percentcomplete(perc) : 0;
280         todo->start  = to_date(inst->start);
281         todo->due    = to_date(icalcomponent_get_due(inst->comp));
282         todo->cal    = inst->cal;
283         return todo;
284 }
285
286 static todo_t *to_todos(icalarray *array)
287 {
288         todo_t  list = {};
289         todo_t *tail = &list;
290         for (int i = 0; i < array->num_elements; i++) {
291                  ical_inst *inst = icalarray_element_at(array, i);
292                  tail->next = to_todo(inst);
293                  tail = tail->next;
294         }
295         return list.next;
296 }
297
298 static void print_todos(todo_t *start)
299 {
300         for (todo_t *cur = start; cur; cur = cur->next)
301                 printf("%04d-%02d-%02d %02d:%02d - %d%% - %s\n",
302                         cur->due.year, cur->due.month, cur->due.day,
303                         cur->due.hour, cur->due.min,   cur->status,
304                         cur->name ?: cur->desc ?: "[no summary]");
305 }
306
307 /* Config parser */
308 void ical_config(const char *group, const char *name, const char *key, const char *value)
309 {
310         ical_t *cal = NULL, *last = NULL;
311
312         /* Make sure it's valid */
313         if (!match(group, "ical") || !name)
314                 return;
315
316         /* Find existing calendar */
317         for (cal = calendars; cal; last = cal, cal = cal->next)
318                 if (match(cal->cal.name, name))
319                         break;
320
321         /* Create new calendar */
322         if (!cal) {
323                 cal = new0(ical_t);
324                 cal->cal.type = "ical";
325                 cal->cal.name = get_name(name);
326                 if (last)
327                         last->next = cal;
328                 else
329                         calendars = cal;
330         }
331
332         /* Set calendar values */
333         if (match(key, "location"))
334                 cal->location = get_string(value);
335         else if (match(key, "username"))
336                 cal->username = get_string(value);
337         else if (match(key, "password"))
338                 cal->password = get_string(value);
339 }
340
341 /* Cal functions */
342 cal_t *ical_cals(void)
343 {
344         read_icals();
345
346         for (ical_t *cal = calendars; cal; cal = cal->next)
347                 cal->cal.next = &cal->next->cal;
348
349         return &calendars->cal;
350 }
351
352 /* Event functions */
353 event_t *ical_events(date_t _start, date_t _end)
354 {
355         read_icals();
356
357         icaltimetype start = to_itime(_start);
358         icaltimetype end   = to_itime(_end);
359         icalarray *array = icalarray_new(sizeof(ical_inst), 1);
360         for (ical_t *cal = calendars; cal; cal = cal->next)
361                 add_recur(&cal->cal, array, cal->comp, start, end, ICAL_VEVENT_COMPONENT);
362         icalarray_sort(array, ical_compare);
363         event_t *events = to_events(array);
364         icalarray_free(array);
365
366         return events;
367 }
368
369 /* Todo functions */
370 todo_t *ical_todos(date_t _start, date_t _end)
371 {
372         read_icals();
373
374         icaltimetype start = to_itime(_start);
375         icaltimetype end   = to_itime(_end);
376         icalarray *array = icalarray_new(sizeof(ical_inst), 1);
377         for (ical_t *cal = calendars; cal; cal = cal->next)
378                 add_recur(&cal->cal, array, cal->comp, start, end, ICAL_VTODO_COMPONENT);
379         icalarray_sort(array, ical_compare);
380         todo_t *todos = to_todos(array);
381         icalarray_free(array);
382
383         return todos;
384 }
385
386 /* Edit functions */
387 void ical_edit(edit_t mode)
388 {
389         switch (mode) {
390                 case EDIT_NONE:
391                         break;
392                 case EDIT_CAL:
393                         //load_username();
394                         //load_password();
395                         form_show(&form_cal);
396                         break;
397                 case EDIT_EVENT:
398                         ff_name.text   = strcopy(EVENT->name);
399                         ff_desc.text   = strcopy(EVENT->desc);
400                         ff_loc.text    = strcopy(EVENT->loc);
401                         //ff_cat.text    = strcopy(EVENT->cat);
402                         ff_start.date  = EVENT->start;
403                         ff_end.date    = EVENT->end;
404                         //ff_recur.text = strcopy(EVENT->recur);
405                         debug("ical_edit - event");
406                         form_show(&form_event);
407                         break;
408                 case EDIT_TODO:
409                         ff_name.text   = strcopy(TODO->name);
410                         ff_desc.text   = strcopy(TODO->desc);
411                         ff_status.number = TODO->status;
412                         //ff_cat.text    = strcopy(TODO->cat);
413                         ff_start.date  = TODO->start;
414                         ff_due.date    = TODO->due;
415                         //ff_recur.text = strcopy(TODO->recur);
416                         debug("ical_edit - todo");
417                         form_show(&form_todo);
418                         break;
419         }
420 }
421
422 void ical_save(edit_t mode)
423 {
424         //if (isevent || istodo) {
425         //      //save_title();
426         //      //save_location();
427         //      //save_recur();
428         //      //save_details();
429         //}
430         //if (isevent) {
431         //      //save_start();
432         //      //save_end();
433         //}
434         //if (istodo) {
435         //      //save_due();
436         //      //save_completed();
437         //}
438         //if (iscal) {
439         //      //save_username();
440         //      //save_password();
441         //}
442 }
443
444 /* Test functions */
445 void ical_printr(icalcomponent *comp, int depth)
446 {
447         /* Print component */
448         icalcomponent_kind kind = icalcomponent_isa(comp);
449         printf("%*s", depth, "");
450         printf("%s",  icalcomponent_kind_to_string(kind));
451         if (kind == ICAL_VEVENT_COMPONENT ||
452             kind == ICAL_VTODO_COMPONENT)
453                 printf(" - %s", icalcomponent_get_summary(comp) ?: "[no summary]");
454         printf("\n");
455
456         /* Print children */
457         icalcomponent_kind find = ICAL_ANY_COMPONENT;
458         icalcomponent *child = icalcomponent_get_first_component(comp, find);
459         while (child) {
460                 ical_printr(child, depth+2);
461                 child = icalcomponent_get_next_component(comp, find);
462         }
463 }
464
465 void ical_test(char *path)
466 {
467         /* Load ical */
468         FILE *file = fopen(path, "r");
469         icalparser *parser = icalparser_new();
470         icalparser_set_gen_data(parser, file);
471         icalcomponent *comp = icalparser_parse(parser, (void*)fgets);
472
473         /* Misc */
474         icalarray *array;
475         icaltimetype start = {.year = 2000};
476         icaltimetype end   = {.year = 2020};
477
478         /* Find events */
479         array = icalarray_new(sizeof(ical_inst), 1);
480         add_recur(NULL, array, comp, start, end, ICAL_VEVENT_COMPONENT);
481         icalarray_sort(array, ical_compare);
482         event_t *events = to_events(array);
483         icalarray_free(array);
484
485         /* Find Todos */
486         array = icalarray_new(sizeof(ical_inst), 1);
487         add_recur(NULL, array, comp, start, end, ICAL_VTODO_COMPONENT);
488         icalarray_sort(array, ical_compare);
489         todo_t *todos = to_todos(array);
490         icalarray_free(array);
491
492         /* Print */
493         ical_printr(comp, 0);
494         print_events(events);
495         print_todos(todos);
496
497         (void)print_events;
498         (void)print_todos;
499         (void)events;
500         (void)todos;
501
502         /* Cleanup */
503         icalparser_free(parser);
504 }