agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafs| Log | Files | Refs | README | LICENSE | Mail | Website |
ical_extra.c (12676B)
1 #include "ical_extra.h"
2 #include "libical/ical.h"
3 #include "arena.h"
4 #include "path.h"
5 #include "sys/stat.h"
6 #include "util.h"
7 #include "uuid/uuid.h"
8 #include <stdbool.h>
9 const char *IS_DIRECTORY_PROPERTY = "X-CALDAVFS-ISDIRECTORY";
10 const char *FILE_EXTENSION_PROPERTY = "X-CALDAVFS-FILEEXT";
11 const char *CUSTOM_PROPERTY_PREFIX = "X-CALDAVFS-CUSTOM-";
12 size_t CUSTOM_PROPERTY_PREFIX_LEN = 18;
13
14 const char *
15 icalcomponent_get_uniq_x_value(icalcomponent *component, const char *key)
16 {
17 icalcomponent *inner =
18 icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
19 if (!inner) {
20 return NULL;
21 }
22
23 for (icalproperty *prop =
24 icalcomponent_get_first_property(inner, ICAL_X_PROPERTY);
25 prop != NULL;
26 prop = icalcomponent_get_next_property(inner, ICAL_X_PROPERTY)) {
27
28 const char *prop_name = icalproperty_get_x_name(prop);
29 if (prop_name && strcmp(prop_name, key) == 0) {
30 LOG("FOUND PROP %s", key);
31 return icalproperty_get_value_as_string(prop);
32 }
33 }
34 return NULL;
35 }
36
37 icaltimetype
38 get_last_modified(icalcomponent *component)
39 {
40 icalproperty *last_modified_prop = icalcomponent_get_next_property(
41 component, ICAL_LASTMODIFIED_PROPERTY);
42
43 if (last_modified_prop) {
44 icaltimetype ical_lastmodified =
45 icalproperty_get_lastmodified(last_modified_prop);
46 return ical_lastmodified;
47 }
48 else {
49 return icalcomponent_get_dtstamp(component);
50 }
51 }
52
53 const icalproperty_class
54 parse_ical_class(const char *input)
55 {
56 if (strcmp(input, "private") == 0) {
57 return ICAL_CLASS_PRIVATE;
58 }
59 else if (strcmp(input, "public") == 0) {
60 return ICAL_CLASS_PUBLIC;
61 }
62 else if (strcmp(input, "confidential") == 0) {
63 return ICAL_CLASS_CONFIDENTIAL;
64 }
65 return ICAL_CLASS_NONE;
66 }
67
68 const icalproperty_status
69 parse_ical_status(const char *input)
70 {
71 // FIXME should check if VJOURNAL, VEVENT or VTODO
72 if (strcmp(input, "draft") == 0) {
73 return ICAL_STATUS_DRAFT;
74 }
75 else if (strcmp(input, "final") == 0) {
76 return ICAL_STATUS_FINAL;
77 }
78 return ICAL_STATUS_FINAL;
79 }
80
81 const char *
82 format_ical_class(const enum icalproperty_class iclass)
83 {
84 if (iclass == ICAL_CLASS_PRIVATE) {
85 return "private";
86 }
87 else if (iclass == ICAL_CLASS_PUBLIC) {
88 return "public";
89 }
90 else if (iclass == ICAL_CLASS_CONFIDENTIAL) {
91 return "confidential";
92 }
93 return NULL;
94 }
95
96 const char *
97 format_ical_status(const enum icalproperty_status istatus)
98 {
99 if (istatus == ICAL_STATUS_DRAFT) {
100 return "draft";
101 }
102 else if (istatus == ICAL_STATUS_FINAL) {
103 return "final";
104 }
105 return NULL;
106 }
107
108 bool
109 is_directory_component(icalcomponent *component)
110 {
111 icalcomponent *journal = icalcomponent_get_first_component(
112 component, ICAL_VJOURNAL_COMPONENT);
113
114 for (icalproperty *prop = icalcomponent_get_first_property(
115 journal, ICAL_RELATEDTO_PROPERTY);
116 prop != NULL; prop = icalcomponent_get_next_property(
117 journal, ICAL_RELATEDTO_PROPERTY)) {
118
119 const char *reltype =
120 icalproperty_get_parameter_as_string(prop, "RELTYPE");
121 if (reltype && strcasecmp(reltype, "CHILD") == 0) {
122 return true;
123 }
124 }
125 const char *is_directory =
126 icalcomponent_get_uniq_x_value(component, IS_DIRECTORY_PROPERTY);
127 return is_directory && strcmp(is_directory, "YES") == 0;
128 }
129
130 icalcomponent *
131 parse_ics_file(arena *ar, const char *filename)
132 {
133
134 FILE *file = fopen(filename, "r");
135 LOG("filename is %s", filename);
136
137 struct stat st;
138 // Each node corresponds to a file on the system.
139 assert(stat(filename, &st) == 0);
140
141 size_t size = st.st_size;
142 char *buffer = rmalloc(ar, size + 1);
143
144 size_t bytes_read = fread(buffer, 1, size, file);
145 buffer[bytes_read] = '\0';
146 fclose(file);
147
148 icalcomponent *component = ricalcomponent_new_from_string(ar, buffer);
149
150 return component;
151 }
152
153 icaltimetype
154 get_ical_now()
155 {
156 return icaltime_from_timet_with_zone(time(NULL), 0,
157 icaltimezone_get_utc_timezone());
158 }
159
160 size_t
161 icalcomponent_get_description_size(icalcomponent *component)
162 {
163 const char *description = icalcomponent_get_description(component);
164 if (!description) {
165 return 0;
166 }
167 else {
168 size_t size = strlen(description);
169 return size;
170 }
171 }
172
173 char *
174 create_new_unique_ics_uid(arena *ar)
175 {
176 uuid_t uuid;
177 char uuid_str[37]; // UUIDs are 36 characters + null terminator
178 uuid_generate(uuid);
179 uuid_unparse(uuid, uuid_str);
180 char *res = NULL;
181 size_t wRes = rasprintf(ar, &res, "%s-caldavfs", uuid_str);
182 assert(wRes != -1);
183
184 return res;
185 }
186
187 icalcomponent *
188 create_vjournal_directory(arena *ar, const char *summary)
189 {
190
191 icalcomponent *calendar = icalcomponent_new_vcalendar();
192
193 icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
194 icalcomponent_add_property(calendar,
195 icalproperty_new_prodid("-//caldavfs//EN"));
196
197 icalcomponent *journal = icalcomponent_new_vjournal();
198 char *id = create_new_unique_ics_uid(ar);
199
200 icalcomponent_add_property(journal, icalproperty_new_uid(id));
201
202 icalcomponent_add_property(
203 journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
204 icaltimezone_get_utc_timezone())));
205 icalcomponent_add_property(journal, icalproperty_new_summary(summary));
206
207 icalproperty *private = icalproperty_new_class(ICAL_CLASS_PRIVATE);
208 icalcomponent_add_property(journal, private);
209 icalcomponent_set_status(journal, ICAL_STATUS_FINAL);
210
211 icalproperty *is_directory = icalproperty_new_x(IS_DIRECTORY_PROPERTY);
212 icalproperty_set_x_name(is_directory, IS_DIRECTORY_PROPERTY);
213 icalproperty_set_x(is_directory, "YES");
214 icalcomponent_add_property(journal, is_directory);
215
216 icalcomponent_add_component(calendar, journal);
217 return calendar;
218 }
219
220 icalcomponent *
221 create_vjournal_entry(arena *ar, const char *summary)
222 {
223
224 icalcomponent *calendar = ricalcomponent_new_vcalendar(ar);
225
226 icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
227 icalcomponent_add_property(calendar,
228 icalproperty_new_prodid("-//caldavfs//EN"));
229
230 // Step 2: Create a VJOURNAL entry
231 icalcomponent *journal = icalcomponent_new_vjournal();
232 char *id = create_new_unique_ics_uid(ar);
233
234 icalcomponent_add_property(journal, icalproperty_new_uid(id));
235 icalcomponent_add_property(journal,
236 icalproperty_new_class(ICAL_CLASS_PRIVATE));
237
238 icalcomponent_add_property(
239 journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
240 icaltimezone_get_utc_timezone())));
241 char *file_extension = get_file_extension(ar, summary);
242 if (!file_extension) {
243 file_extension = "";
244 }
245 icalproperty *private = icalproperty_new_class(ICAL_CLASS_PRIVATE);
246 icalcomponent_add_property(journal, private);
247
248 icalcomponent_set_status(journal, ICAL_STATUS_DRAFT);
249
250 char *without_extension = without_file_extension(ar, summary);
251
252 icalcomponent_add_property(journal,
253 icalproperty_new_summary(without_extension));
254 icalcomponent_set_file_extension(journal, file_extension);
255
256 icalcomponent_add_property(journal, icalproperty_new_description(""));
257
258 icalcomponent_add_component(calendar, journal);
259
260 return calendar;
261 }
262
263 const char *
264 get_parent_uid(icalcomponent *component)
265 {
266 icalcomponent *inner =
267 icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
268
269 for (icalproperty *prop = icalcomponent_get_first_property(
270 inner, ICAL_RELATEDTO_PROPERTY);
271 prop != NULL; prop = icalcomponent_get_next_property(
272 inner, ICAL_RELATEDTO_PROPERTY)) {
273 const char *reltype =
274 icalproperty_get_parameter_as_string(prop, "RELTYPE");
275 if (reltype && strcasecmp(reltype, "PARENT") == 0) {
276 return icalproperty_get_value_as_string(prop);
277 }
278 }
279
280 return NULL;
281 }
282
283 void
284 remove_parent_child_relationship_from_component(icalcomponent *parent,
285 icalcomponent *child)
286 {
287
288 const char *parent_uid = icalcomponent_get_uid(parent);
289
290 icalcomponent *inner =
291 icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
292
293 icalproperty *prop =
294 icalcomponent_get_first_property(inner, ICAL_RELATEDTO_PROPERTY);
295 while (prop != NULL) {
296 icalproperty *next = icalcomponent_get_next_property(
297 inner, ICAL_RELATEDTO_PROPERTY);
298 const char *value = icalproperty_get_relatedto(prop);
299 icalparameter *reltype = icalproperty_get_first_parameter(
300 prop, ICAL_RELTYPE_PARAMETER);
301
302 if (value && strcmp(value, parent_uid) == 0 && reltype &&
303 icalparameter_get_reltype(reltype) == ICAL_RELTYPE_PARENT) {
304 icalcomponent_remove_property(inner, prop);
305 icalproperty_free(prop);
306 }
307
308 prop = next;
309 }
310 }
311
312 void
313 set_parent_child_relationship_to_component(icalcomponent *parent,
314 icalcomponent *child)
315 {
316 icalproperty *child_related_to_parent =
317 icalproperty_new_relatedto(icalcomponent_get_uid(parent));
318 icalparameter *reltype_child =
319 icalparameter_new_reltype(ICAL_RELTYPE_PARENT);
320 icalproperty_add_parameter(child_related_to_parent, reltype_child);
321
322 icalcomponent *inner =
323 icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
324 if (inner) {
325 icalcomponent_add_property(inner, child_related_to_parent);
326 }
327 }
328
329 void
330 icalcomponent_insert_description(arena *ar, icalcomponent *ic,
331 const char *buf, size_t size, off_t offset)
332 {
333
334 const char *old_desc = icalcomponent_get_description(ic);
335 if (!old_desc)
336 old_desc = "";
337
338 char *new_desc = rstrins(ar, old_desc, offset, buf, size);
339 icalcomponent_set_description(ic, new_desc);
340 }
341
342 icalcomponent *
343 icalcomponent_get_innermost(icalcomponent *c)
344 {
345 icalcomponent *inner =
346 icalcomponent_get_first_component(c, ICAL_ANY_COMPONENT);
347
348 if (!inner) {
349 inner = c;
350 }
351 return inner;
352 }
353
354 void
355 icalcomponent_remove_x_prop(icalcomponent *component, const char *key)
356 {
357 icalcomponent *inner = icalcomponent_get_innermost(component);
358 assert(inner);
359
360 icalproperty *prop_iter =
361 icalcomponent_get_first_property(inner, ICAL_X_PROPERTY);
362 while (prop_iter != NULL) {
363 icalproperty *next_prop =
364 icalcomponent_get_next_property(inner, ICAL_X_PROPERTY);
365 const char *prop_name = icalproperty_get_x_name(prop_iter);
366 if (prop_name && strcmp(prop_name, key) == 0) {
367 icalcomponent_remove_property(inner, prop_iter);
368 icalproperty_free(prop_iter);
369 break;
370 }
371 prop_iter = next_prop;
372 }
373 }
374
375 void
376 icalcomponent_print_x_props(FILE *memstream, icalcomponent *component)
377 {
378 icalcomponent *inner = icalcomponent_get_innermost(component);
379 icalproperty *prop_iter =
380 icalcomponent_get_first_property(inner, ICAL_X_PROPERTY);
381 while (prop_iter != NULL) {
382 icalproperty *next_prop =
383 icalcomponent_get_next_property(inner, ICAL_X_PROPERTY);
384 const char *prop_name = icalproperty_get_x_name(prop_iter);
385 if (starts_with_str(prop_name, CUSTOM_PROPERTY_PREFIX)) {
386 const char *key =
387 prop_name + CUSTOM_PROPERTY_PREFIX_LEN;
388 fprintf(memstream, "user.%s", key);
389 fputc('\0', memstream);
390 }
391 prop_iter = next_prop;
392 }
393 }
394
395 void
396 icalcomponent_remove_custom_prop(arena *ar, icalcomponent *component,
397 const char *key)
398 {
399
400 char *x_key = NULL;
401 rasprintf(ar, &x_key, "%s%s", CUSTOM_PROPERTY_PREFIX, key);
402 icalcomponent_remove_x_prop(component, x_key);
403 }
404
405 void
406 icalcomponent_set_unique_x_value(icalcomponent *component, const char *key,
407 const char *value)
408 {
409 icalcomponent_remove_x_prop(component, key);
410 icalcomponent *inner = icalcomponent_get_innermost(component);
411 assert(inner);
412
413 icalproperty *fileext_prop = icalproperty_new_x(key);
414
415 icalproperty_set_x_name(fileext_prop, key);
416 icalproperty_set_x(fileext_prop, value);
417
418 icalcomponent_add_property(inner, fileext_prop);
419 }
420 void
421 icalcomponent_set_custom_x_value(arena *ar, icalcomponent *component,
422 const char *key, const char *value)
423 {
424 char *x_key = NULL;
425 rasprintf(ar, &x_key, "%s%s", CUSTOM_PROPERTY_PREFIX, key);
426
427 icalcomponent_set_unique_x_value(component, x_key, value);
428 }
429
430 const char *
431 icalcomponent_get_custom_x_value(arena *ar, icalcomponent *component,
432 const char *key)
433 {
434 char *x_key = NULL;
435 rasprintf(ar, &x_key, "%s%s", CUSTOM_PROPERTY_PREFIX, key);
436
437 return icalcomponent_get_uniq_x_value(component, x_key);
438 }
439
440 // Since empty values are considered null, we store a disallowed
441 // extension (..) to indicate that the system has explicitly not set an
442 // extension.
443 void
444 icalcomponent_set_file_extension(icalcomponent *component,
445 const char *extension)
446 {
447
448 if (strcmp(extension, "") == 0) {
449 icalcomponent_set_unique_x_value(component,
450 FILE_EXTENSION_PROPERTY, ".");
451 }
452 else {
453 icalcomponent_set_unique_x_value(
454 component, FILE_EXTENSION_PROPERTY, extension);
455 }
456 LOG("Set X-CALDAVFS-FILEEXT to '%s' on component.", extension);
457 }
458
459 const char *
460 icalcomponent_get_file_extension(icalcomponent *component)
461 {
462 const char *ext =
463 icalcomponent_get_uniq_x_value(component, FILE_EXTENSION_PROPERTY);
464
465 if (ext && strcmp(ext, ".") == 0)
466 return "";
467 return ext;
468 }
469
470 void
471 icalcomponent_mark_as_directory(icalcomponent *ic)
472 {
473 icalcomponent_set_unique_x_value(ic, IS_DIRECTORY_PROPERTY, "YES");
474 }