agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs

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 }