agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs

fuse_node.c (19941B)

      1 #include "agenda_entry.h"
      2 #include "arena.h"
      3 #include "fuse_node_store.h"
      4 #include "hashmap.h"
      5 #include "ical_extra.h"
      6 #include "path.h"
      7 #include "tree.h"
      8 #include "util.h"
      9 #include <dirent.h>
     10 #include <libical/ical.h>
     11 #include <pthread.h>
     12 #include <regex.h>
     13 #include <stdbool.h>
     14 #include <stdio.h>
     15 #include <stdlib.h>
     16 #include <string.h>
     17 #include <sys/inotify.h>
     18 #include <sys/stat.h>
     19 #include <time.h>
     20 #include <unistd.h>
     21 #include <uuid/uuid.h>
     22 #include <wordexp.h>
     23 
     24 struct tree_node *
     25 get_node_by_uuid(arena *ar, const char *target_uuid)
     26 {
     27 	char *filename_vdir = NULL;
     28 
     29 	// Filenames are uid.ics, so we levarage that
     30 	size_t wRes = rasprintf(ar, &filename_vdir, "%s.ics", target_uuid);
     31 	assert(wRes != -1);
     32 
     33 	LOG("Looking for UID: '%s'", target_uuid);
     34 	LOG("Full filename: '%s'", filename_vdir);
     35 
     36 	struct tree_node *node = get_fuse_node_from_vdir_name(filename_vdir);
     37 	LOG("Found it: %b", node != NULL);
     38 
     39 	return node;
     40 }
     41 
     42 struct tree_node *
     43 get_node_by_path(arena *ar, const char *path)
     44 {
     45 	if (strcmp(path, "/") == 0) {
     46 		LOG("Is ROOT %s", path);
     47 		return fuse_root;
     48 	}
     49 
     50 	char *segments[255];
     51 	size_t count = split_path(ar, path, segments);
     52 
     53 	struct tree_node *current = fuse_root;
     54 
     55 	for (size_t i = 0; i < count && current; ++i) {
     56 		const char *segment = segments[i];
     57 		struct tree_node *next = NULL;
     58 		LOG("Segment: %s", segment);
     59 
     60 		for (size_t j = 0; j < current->child_count; ++j) {
     61 
     62 			const char *child_name =
     63 			    get_node_filename(current->children[j]);
     64 			if (strcmp(child_name, segment) == 0) {
     65 
     66 				next = current->children[j];
     67 				break;
     68 			}
     69 		}
     70 		if (next == NULL) {
     71 			return NULL;
     72 		}
     73 		current = next;
     74 	}
     75 	return current;
     76 }
     77 
     78 bool
     79 node_is_directory(arena *ar, const struct tree_node *node,
     80 		  icalcomponent *node_ics)
     81 {
     82 	if (is_root_node(node) || node_has_children(node)) {
     83 		return true;
     84 	}
     85 	else {
     86 		// When user creates a directory, a directory component is
     87 		// stored in the ics file. So even if a node has no children
     88 		// it can still be that it should be shown as a directory.
     89 		return is_directory_component(node_ics);
     90 	}
     91 }
     92 
     93 size_t
     94 write_ical_file(arena *ar, const struct tree_node *node, icalcomponent *ic)
     95 {
     96 
     97 	// Always set last modified to the time we write
     98 	icalproperty *prop =
     99 	    icalcomponent_get_first_property(ic, ICAL_LASTMODIFIED_PROPERTY);
    100 	if (!prop) {
    101 		prop = icalproperty_new_lastmodified(get_ical_now());
    102 		icalcomponent_add_property(ic, prop);
    103 	}
    104 	else {
    105 		icalproperty_set_lastmodified(prop, get_ical_now());
    106 	}
    107 
    108 	icalcomponent *inner = icalcomponent_get_inner(ic);
    109 
    110 	const char *descr_prop = icalcomponent_get_description(inner);
    111 	if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
    112 		icalproperty *ical_descr_prop =
    113 		    icalcomponent_get_first_property(inner,
    114 						     ICAL_DESCRIPTION_PROPERTY);
    115 		icalcomponent_remove_property(inner, ical_descr_prop);
    116 	}
    117 
    118 	char *ical_str = ricalcomponent_as_ical_string_r(ar, ic);
    119 
    120 	int res = write_to_file(get_vdir_filepath(ar, node), ical_str);
    121 
    122 	return res;
    123 }
    124 
    125 // Owner: ctx
    126 icalcomponent *
    127 get_icalcomponent_from_node(arena *ar, const struct tree_node *n)
    128 {
    129 	if (is_root_node(n)) {
    130 		return NULL;
    131 	}
    132 
    133 	const char *orig_path = get_vdir_filepath(ar, n);
    134 	// TODO: Cache intermediate results within context
    135 	icalcomponent *ic = parse_ics_file(ar, orig_path);
    136 	return ic;
    137 }
    138 
    139 int
    140 write_parent_child_components(arena *ar, const struct tree_node *parent,
    141 			      icalcomponent *iparent,
    142 			      const struct tree_node *child,
    143 			      icalcomponent *ichild)
    144 {
    145 	LOG("Adding parent and child relation");
    146 	set_parent_child_relationship_to_component(iparent, ichild);
    147 	return write_ical_file(ar, child, ichild);
    148 }
    149 
    150 // Writes ics component summary to the filename of the tree_node
    151 int
    152 update_summary(arena *ar, icalcomponent *ics, const struct tree_node *node)
    153 {
    154 	if (is_directory_component(ics)) {
    155 		icalcomponent_set_summary(ics, get_node_filename(node));
    156 	}
    157 	else {
    158 		char *filename_no_ext =
    159 		    without_file_extension(ar, get_node_filename(node));
    160 		icalcomponent_set_summary(ics, filename_no_ext);
    161 	}
    162 
    163 	return write_ical_file(ar, node, ics);
    164 }
    165 
    166 int
    167 update_node_file_extension(arena *ar, icalcomponent *ics,
    168 			   const struct tree_node *node)
    169 {
    170 	const char *ext = get_file_extension(ar, get_node_filename(node));
    171 	if (!ext) {
    172 		return 0;
    173 	}
    174 	icalcomponent_set_file_extension(ics, ext);
    175 	return write_ical_file(ar, node, ics);
    176 }
    177 
    178 struct agenda_entry *
    179 parse_ics_to_agenda_entry(arena *ar, const char *vdir_filepath)
    180 {
    181 	icalcomponent *component = parse_ics_file(ar, vdir_filepath);
    182 	if (component == NULL) {
    183 		LOG("No component");
    184 		return NULL;
    185 	}
    186 	icalcomponent *journal =
    187 	    icalcomponent_get_first_real_component(component);
    188 
    189 	if (journal == NULL ||
    190 	    icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
    191 		LOG("Not journal component");
    192 		return NULL;
    193 	}
    194 	const char *summary = icalcomponent_get_summary(component);
    195 	if (summary == NULL) {
    196 		LOG("No summary found");
    197 		return NULL;
    198 	}
    199 
    200 	char *filename = NULL;
    201 	if (is_directory_component(component)) {
    202 		filename = rstrdup(ar, summary);
    203 	}
    204 	else {
    205 		LOG("GETTING FILE EXT");
    206 		const char *extension =
    207 		    icalcomponent_get_file_extension(component);
    208 		if (!extension) {
    209 			LOG("Has none, setting to default");
    210 			extension = get_default_file_extension();
    211 		}
    212 
    213 		if (strcmp(extension, "") == 0) {
    214 			filename = rstrdup(ar, summary);
    215 		}
    216 		else {
    217 			rasprintf(ar, &filename, "%s.%s", summary, extension);
    218 		}
    219 	}
    220 
    221 	struct agenda_entry *e =
    222 	    create_agenda_entry(ar, filename, get_filename(vdir_filepath));
    223 
    224 	return e;
    225 }
    226 
    227 // Owner: arena
    228 struct agenda_entry *
    229 load_agenda_entry_from_ics_file(arena *ar, const char *filename)
    230 {
    231 	path *filepath = append_path(ar, VDIR, filename);
    232 	LOG("Filepath is %s", filepath);
    233 
    234 	struct stat fileStat;
    235 	if (stat(filepath, &fileStat) == -1) {
    236 		LOG("Can not stat %s.", filepath);
    237 		return NULL;
    238 	}
    239 
    240 	struct agenda_entry *new_entry =
    241 	    parse_ics_to_agenda_entry(ar, filepath);
    242 	if (!new_entry) {
    243 		LOG("Could not parse entry");
    244 		return NULL;
    245 	}
    246 
    247 	LOG("Parsed %s to file %s", new_entry->filename_vdir,
    248 	    new_entry->filename);
    249 	return new_entry;
    250 }
    251 
    252 void
    253 load_root_node_tree()
    254 {
    255 	entries_vdir = hashmap_new(NULL);
    256 	fuse_root = create_tree_node(NULL, NULL);
    257 	arena *ar = create_arena();
    258 	LOG("Loading journal entries from: %s\n", VDIR);
    259 
    260 	DIR *dir = opendir(VDIR);
    261 	if (!dir) {
    262 		perror("opendir");
    263 		return;
    264 	}
    265 
    266 	struct dirent *entry;
    267 	while ((entry = readdir(dir))) {
    268 		if (entry->d_type == DT_UNKNOWN) {
    269 			printf("Unsupported file type for '%s'. Skipping\n",
    270 			       entry->d_name);
    271 		}
    272 		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
    273 			LOG("Loading %s", entry->d_name);
    274 			struct agenda_entry *new_entry =
    275 			    load_agenda_entry_from_ics_file(ar, entry->d_name);
    276 			if (new_entry) {
    277 				create_fuse_node(new_entry);
    278 				LOG("Inserted filename_vdir: %s, %s",
    279 				    new_entry->filename,
    280 				    new_entry->filename_vdir);
    281 			}
    282 			else {
    283 				LOG("Skipping %s", entry->d_name);
    284 			}
    285 		}
    286 	}
    287 	closedir(dir);
    288 	LOG("Set up directories according to parent-child");
    289 
    290 	size_t n_keys = 0;
    291 	char **keys = hashmap_get_keys(entries_vdir, &n_keys);
    292 	for (size_t i = 0; i < n_keys; i++) {
    293 		const char *vdirname = keys[i];
    294 		struct tree_node *child =
    295 		    get_fuse_node_from_vdir_name(vdirname);
    296 
    297 		LOG("Parsing %s", vdirname);
    298 		icalcomponent *ic = get_icalcomponent_from_node(ar, child);
    299 		LOG("Success");
    300 
    301 		const char *parent_uid = get_parent_uid(ic);
    302 		if (parent_uid) {
    303 			LOG("Has parent");
    304 
    305 			struct tree_node *parent =
    306 			    get_node_by_uuid(ar, parent_uid);
    307 			if (parent) {
    308 				icalcomponent *pic =
    309 				    get_icalcomponent_from_node(ar, parent);
    310 				if (!is_directory_component(pic)) {
    311 					icalcomponent_mark_as_directory(pic);
    312 					assert(write_ical_file(ar, parent,
    313 							       pic) == 0);
    314 				}
    315 
    316 				move_fuse_node(parent, child);
    317 			}
    318 			else {
    319 				LOG("COULD NOT FIND PARENT NODE: %s",
    320 				    parent_uid);
    321 			}
    322 		}
    323 		else {
    324 			add_fuse_child(fuse_root, child);
    325 		}
    326 	}
    327 	LOG("Inserted %zu entries", n_keys);
    328 
    329 	hashmap_free_keys(keys, n_keys);
    330 	free_all(ar);
    331 
    332 	LOG("Done");
    333 }
    334 
    335 // Sets a validated dtstart
    336 int
    337 set_dtstart(arena *ar, const char *dtstart_c, const struct tree_node *node)
    338 {
    339 	struct icaltimetype dtstart = icaltime_from_string(dtstart_c);
    340 	if (icaltime_compare(dtstart, icaltime_null_time()) == 0) {
    341 		return -EINVAL;
    342 	}
    343 
    344 	icalcomponent *comp = get_icalcomponent_from_node(ar, node);
    345 
    346 	icalcomponent_set_dtstart(comp, dtstart);
    347 
    348 	return write_ical_file(ar, node, comp);
    349 }
    350 
    351 const char *
    352 get_dtstart(arena *ar, struct tree_node *n)
    353 {
    354 
    355 	icalcomponent *comp = get_icalcomponent_from_node(ar, n);
    356 
    357 	struct icaltimetype dtstart = icalcomponent_get_dtstart(comp);
    358 	if (icaltime_compare(dtstart, icaltime_null_time()) == 0) {
    359 		return NULL;
    360 	}
    361 
    362 	const char *time = icaltime_as_ical_string(dtstart);
    363 	return time;
    364 }
    365 
    366 int
    367 clear_dtstart(arena *ar, struct tree_node *node)
    368 {
    369 	icalcomponent *comp = get_icalcomponent_from_node(ar, node);
    370 
    371 	icalproperty *p =
    372 	    icalcomponent_get_first_property(comp, ICAL_DTSTART_PROPERTY);
    373 
    374 	if (p) {
    375 		icalcomponent_remove_property(comp, p);
    376 	}
    377 
    378 	return write_ical_file(ar, node, comp);
    379 }
    380 
    381 static int
    382 append_category_to_memstream(FILE *memstream, const char *category,
    383 			     bool is_first_category)
    384 {
    385 	int res = 0;
    386 	if (!is_first_category) {
    387 		res = fputs(",", memstream);
    388 		assert(res != EOF);
    389 	}
    390 
    391 	res = fputs(category, memstream);
    392 	assert(res != EOF);
    393 	return 0;
    394 }
    395 
    396 // Returns comma separated list of categories for a file
    397 char *
    398 get_node_categories(arena *ar, const struct tree_node *node)
    399 {
    400 	// TODO: Take this as parameter instead...
    401 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    402 	if (!component) {
    403 		return NULL;
    404 	}
    405 
    406 	FILE *memstream = NULL;
    407 	char *result_buffer = NULL;
    408 	arena_register(ar, result_buffer, free);
    409 	size_t buffer_size = 0;
    410 
    411 	memstream = open_memstream(&result_buffer, &buffer_size);
    412 
    413 	icalcomponent *inner = icalcomponent_get_inner(component);
    414 
    415 	icalproperty *catp =
    416 	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
    417 
    418 	bool first_category = true;
    419 	while (catp) {
    420 		const char *category = icalproperty_get_categories(catp);
    421 		append_category_to_memstream(memstream, category,
    422 					     first_category);
    423 		catp = icalcomponent_get_next_property(
    424 		    inner, ICAL_CATEGORIES_PROPERTY);
    425 
    426 		first_category = false;
    427 	}
    428 
    429 	size_t res = fclose(memstream);
    430 	assert(res != EOF);
    431 
    432 	return result_buffer;
    433 }
    434 
    435 int
    436 delete_node_categories(arena *ar, const struct tree_node *node)
    437 {
    438 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    439 	if (!component) {
    440 		return 0;
    441 	}
    442 
    443 	icalcomponent *inner = icalcomponent_get_inner(component);
    444 
    445 	icalproperty *catp =
    446 	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
    447 
    448 	// Delete old categories
    449 	while (catp) {
    450 		icalcomponent_remove_property(inner, catp);
    451 
    452 		catp = icalcomponent_get_next_property(
    453 		    inner, ICAL_CATEGORIES_PROPERTY);
    454 	}
    455 
    456 	return write_ical_file(ar, node, component);
    457 }
    458 
    459 int
    460 set_node_categories(arena *ar, const struct tree_node *node,
    461 		    const char *new_categories, size_t s)
    462 {
    463 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    464 	if (!component) {
    465 		return 0;
    466 	}
    467 
    468 	icalcomponent *inner = icalcomponent_get_inner(component);
    469 
    470 	icalproperty *catp =
    471 	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
    472 
    473 	// Delete old categories
    474 	while ((catp = icalcomponent_get_first_property(
    475 		    inner, ICAL_CATEGORIES_PROPERTY)) != NULL) {
    476 		icalcomponent_remove_property(inner, catp);
    477 	}
    478 
    479 	if (s == 0) {
    480 		return write_ical_file(ar, node, component);
    481 	}
    482 
    483 	char *null_terminated_categories = rstrndup(ar, new_categories, s);
    484 
    485 	icalproperty *new_cat_prop =
    486 	    icalproperty_new_categories(null_terminated_categories);
    487 	icalcomponent_add_property(inner, new_cat_prop);
    488 
    489 	return write_ical_file(ar, node, component);
    490 }
    491 
    492 const char *
    493 get_node_class(arena *ar, const struct tree_node *node)
    494 {
    495 
    496 	// TODO: Take this as parameter instead...
    497 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    498 	if (!component) {
    499 		return 0;
    500 	}
    501 
    502 	icalcomponent *inner = icalcomponent_get_inner(component);
    503 
    504 	const icalproperty *classp =
    505 	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
    506 
    507 	if (!classp) {
    508 		return NULL;
    509 	}
    510 
    511 	const enum icalproperty_class classv = icalproperty_get_class(classp);
    512 	return format_ical_class(classv);
    513 }
    514 
    515 const char *
    516 get_node_status(arena *ar, const struct tree_node *node)
    517 {
    518 
    519 	// TODO: Take this as parameter instead...
    520 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    521 	if (!component) {
    522 		return 0;
    523 	}
    524 
    525 	icalcomponent *inner = icalcomponent_get_inner(component);
    526 
    527 	const icalproperty *statusp =
    528 	    icalcomponent_get_first_property(inner, ICAL_STATUS_PROPERTY);
    529 
    530 	if (!statusp) {
    531 		LOG("No status found");
    532 		return NULL;
    533 	}
    534 
    535 	const enum icalproperty_status statusv =
    536 	    icalproperty_get_status(statusp);
    537 	return format_ical_status(statusv);
    538 }
    539 
    540 int
    541 delete_node_class(arena *ar, const struct tree_node *node)
    542 {
    543 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    544 	if (!component) {
    545 		return 0;
    546 	}
    547 	icalcomponent *inner = icalcomponent_get_inner(component);
    548 	icalproperty *prop =
    549 	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
    550 
    551 	icalcomponent_remove_property(inner, prop);
    552 
    553 	return write_ical_file(ar, node, component);
    554 }
    555 
    556 int
    557 set_node_class(arena *ar, const struct tree_node *node,
    558 	       const icalproperty_class new_class)
    559 {
    560 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    561 	if (!component) {
    562 		return 0;
    563 	}
    564 
    565 	icalcomponent *inner = icalcomponent_get_inner(component);
    566 
    567 	icalproperty *classp =
    568 	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
    569 
    570 	icalproperty_set_class(classp, new_class);
    571 	return write_ical_file(ar, node, component);
    572 }
    573 
    574 int
    575 set_node_status(arena *ar, const struct tree_node *node,
    576 		const icalproperty_status new_status)
    577 {
    578 	icalcomponent *component = get_icalcomponent_from_node(ar, node);
    579 	if (!component) {
    580 		return 0;
    581 	}
    582 
    583 	icalcomponent_set_status(component, new_status);
    584 
    585 	return write_ical_file(ar, node, component);
    586 }
    587 
    588 size_t
    589 calculate_node_size(arena *ar, const struct tree_node *node,
    590 		    icalcomponent *node_component)
    591 {
    592 	if (!node_is_directory(ar, node, node_component)) {
    593 		return icalcomponent_get_description_size(node_component);
    594 	}
    595 
    596 	size_t total_size = 0;
    597 	for (int i = 0; i < node->child_count; i++) {
    598 		icalcomponent *cic =
    599 		    get_icalcomponent_from_node(ar, node->children[i]);
    600 		total_size += calculate_node_size(ar, node->children[i], cic);
    601 	}
    602 	return total_size;
    603 }
    604 
    605 struct stat
    606 get_node_stat(arena *ar, const struct tree_node *node,
    607 	      icalcomponent *node_component)
    608 {
    609 	if (is_root_node(node)) {
    610 		struct stat st = {0};
    611 		st.st_gid = getgid();
    612 		st.st_uid = getuid();
    613 		st.st_ino = 0;
    614 		return st;
    615 	}
    616 	else {
    617 		struct stat vdir_stat = {0};
    618 		const char *vdir_path = get_vdir_filepath(ar, node);
    619 		int res = stat(vdir_path, &vdir_stat);
    620 		assert(res == 0);
    621 		if (node_is_directory(ar, node, node_component)) {
    622 			vdir_stat.st_size = (off_t)calculate_node_size(
    623 			    ar, node, node_component);
    624 			vdir_stat.st_mode = S_IFDIR | 0444;
    625 			vdir_stat.st_nlink = 2;
    626 		}
    627 		else {
    628 			vdir_stat.st_size =
    629 			    (off_t)icalcomponent_get_description_size(
    630 				node_component);
    631 
    632 			vdir_stat.st_mode = S_IFREG | 0774;
    633 			vdir_stat.st_nlink = 1;
    634 		}
    635 
    636 		return vdir_stat;
    637 	}
    638 }
    639 
    640 int
    641 insert_fuse_node_to_path(arena *ar, const char *fuse_path,
    642 			 struct tree_node *child_node, icalcomponent *child_ics)
    643 {
    644 	char *parent_path = get_parent_path(ar, fuse_path);
    645 	int status = 0;
    646 	LOG("Parent path is %s", parent_path);
    647 
    648 	struct tree_node *parent_node = get_node_by_path(ar, parent_path);
    649 	if (!parent_node) {
    650 		LOG("Parent does not exist");
    651 		return -ENOENT;
    652 	}
    653 
    654 	icalcomponent *parent_ics =
    655 	    get_icalcomponent_from_node(ar, parent_node);
    656 
    657 	if (!node_is_directory(ar, parent_node, parent_ics)) {
    658 		LOG("Parent is not a directory");
    659 		return -ENOTDIR;
    660 	}
    661 
    662 	if (parent_ics) {
    663 		status = write_parent_child_components(
    664 		    ar, parent_node, parent_ics, child_node, child_ics);
    665 	}
    666 	else {
    667 		write_ical_file(ar, child_node, child_ics);
    668 	}
    669 
    670 	add_child(parent_node, child_node);
    671 	return status;
    672 }
    673 
    674 enum ENTRY_TYPE { ENTRY_DIRECTORY, ENTRY_FILE };
    675 
    676 int
    677 create_entry_from_fuse(arena *ar, const char *fuse_path, enum ENTRY_TYPE etype)
    678 {
    679 	const char *entry_prefix = "/";
    680 	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
    681 		return -EINVAL;
    682 	}
    683 
    684 	const char *new_filename = get_filename(fuse_path);
    685 	icalcomponent *new_component = NULL;
    686 
    687 	switch (etype) {
    688 	case (ENTRY_DIRECTORY):
    689 		new_component = create_vjournal_directory(ar, new_filename);
    690 		break;
    691 	case (ENTRY_FILE):
    692 		new_component = create_vjournal_entry(ar, new_filename);
    693 		break;
    694 	}
    695 
    696 	char *new_filname_vdir = NULL;
    697 	rasprintf(ar, &new_filname_vdir, "%s.ics",
    698 		  icalcomponent_get_uid(new_component));
    699 
    700 	struct agenda_entry *new_entry =
    701 	    create_agenda_entry(ar, new_filename, new_filname_vdir);
    702 
    703 	LOG("Inserting vjournal directory");
    704 
    705 	struct tree_node *new_node = create_fuse_node(new_entry);
    706 
    707 	return insert_fuse_node_to_path(ar, fuse_path, new_node, new_component);
    708 }
    709 
    710 int
    711 update_or_create_fuse_entry_from_vdir(arena *ar, const char *filepath_vdir)
    712 {
    713 	const char *filename_vdir = get_filename(filepath_vdir);
    714 	LOG("Filename original is %s", filename_vdir);
    715 
    716 	struct agenda_entry *updated_entry =
    717 	    load_agenda_entry_from_ics_file(ar, filename_vdir);
    718 
    719 	if (!updated_entry) {
    720 		return -EIO;
    721 	}
    722 
    723 	struct tree_node *node = upsert_fuse_node(updated_entry);
    724 
    725 	icalcomponent *entry_ics = get_icalcomponent_from_node(ar, node);
    726 
    727 	const char *new_parent_uid = get_parent_uid(entry_ics);
    728 
    729 	if (new_parent_uid) {
    730 		LOG("Has parent");
    731 		struct tree_node *new_parent =
    732 		    get_node_by_uuid(ar, new_parent_uid);
    733 		move_fuse_node(new_parent, node);
    734 
    735 		icalcomponent *pic =
    736 		    get_icalcomponent_from_node(ar, new_parent);
    737 		if (!is_directory_component(pic)) {
    738 			icalcomponent_mark_as_directory(pic);
    739 			assert(write_ical_file(ar, new_parent, pic) == 0);
    740 		}
    741 	}
    742 	else {
    743 		LOG("Does not have parent, adding to root");
    744 		move_fuse_node(fuse_root, node);
    745 	}
    746 
    747 	LOG("Tree updated");
    748 	return 0;
    749 }
    750 
    751 int
    752 do_agenda_rename(arena *ar, const char *old, const char *new)
    753 {
    754 	char *new_copy = rstrdup(ar, new);
    755 	const char *new_filename = get_filename(new_copy);
    756 
    757 	const char *old_filename = get_filename(old);
    758 	if (!old_filename)
    759 		return -EINVAL;
    760 
    761 	struct tree_node *new_parent_node =
    762 	    get_node_by_path(ar, get_parent_path(ar, new));
    763 
    764 	if (new_parent_node == NULL) {
    765 		return -EINVAL;
    766 	}
    767 
    768 	struct tree_node *child_node = get_node_by_path(ar, old);
    769 	assert(child_node);
    770 
    771 	icalcomponent *child_ics = get_icalcomponent_from_node(ar, child_node);
    772 	assert(child_ics);
    773 
    774 	icalcomponent *new_parent_ics =
    775 	    get_icalcomponent_from_node(ar, new_parent_node);
    776 
    777 	struct tree_node *old_parent_node = child_node->parent;
    778 	icalcomponent *old_parent_ics =
    779 	    get_icalcomponent_from_node(ar, old_parent_node);
    780 
    781 	set_node_filename(child_node, new_filename);
    782 	update_summary(ar, child_ics, child_node);
    783 	update_node_file_extension(ar, child_ics, child_node);
    784 
    785 	if (old_parent_node && old_parent_ics) {
    786 		remove_parent_child_relationship_from_component(old_parent_ics,
    787 								child_ics);
    788 	}
    789 
    790 	if (new_parent_ics) {
    791 		move_node(new_parent_node, child_node);
    792 		write_parent_child_components(
    793 		    ar, new_parent_node, new_parent_ics, child_node, child_ics);
    794 	}
    795 	else {
    796 		move_node(fuse_root, child_node);
    797 	}
    798 	return 0;
    799 }
    800 
    801 int
    802 delete_vdir_entry(arena *ar, struct tree_node *node)
    803 {
    804 	int res = 0;
    805 
    806 	const char *filepath_original = get_vdir_filepath(ar, node);
    807 
    808 	LOG("Deleting file %s", filepath_original);
    809 
    810 	res = remove(filepath_original);
    811 	if (res == -1) {
    812 		return -EIO;
    813 	}
    814 
    815 	delete_fuse_node(node);
    816 	return res;
    817 }
    818 
    819 // Caller needs to ensure the node has no children.
    820 int
    821 delete_from_vdir_path(arena *ar, const char *filepath)
    822 {
    823 
    824 	struct tree_node *node = get_node_by_uuid(ar, filepath);
    825 	if (!node) {
    826 		return -ENOENT;
    827 	}
    828 
    829 	assert(node->child_count > 0);
    830 	delete_fuse_node(node);
    831 	return 0;
    832 }