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