agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit 702aba8c32d11f478b35e6996138ed8bd8b7811b
parent 985fbcacfe752019b8da39c7660f142c18237466
Author: Marc Coquand <marc@coquand.email>
Date:   Thu, 24 Jul 2025 12:19:46 +0200

Refactor 1

Diffstat:
ACONTRIBUTING.md | 9+++++++++
Magenda_entry.c | 958+------------------------------------------------------------------------------
Magenda_entry.h | 143++-----------------------------------------------------------------------------
Afuse_node.c | 907+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuse_node.h | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuse_node_store.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afuse_node_store.h | 41+++++++++++++++++++++++++++++++++++++++++
Mical_extra.c | 14++++++++++++++
Mical_extra.h | 3+++
Mmain.c | 9+++++----
Mtree.c | 7+++++++
Mtree.h | 3+++
12 files changed, 1215 insertions(+), 1093 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
@@ -0,0 +1,9 @@
+# Contributing
+
+## Memory model
+
+- `memory_region` is for operation-scoped memory (get,read,write)
+- `agenda_entry` heap allocated and must be freed manually
+- Tree nodes free automatically on `free_node`
+
+Avoid mixing `memory_region` pointers with global state.
diff --git a/agenda_entry.c b/agenda_entry.c
@@ -1,9 +1,5 @@
 #include "agenda_entry.h"
-#include "hashmap.h"
-#include "ical_extra.h"
 #include "mregion.h"
-#include "path.h"
-#include "tree.h"
 #include "util.h"
 #include <dirent.h>
 #include <libical/ical.h>
@@ -20,18 +16,6 @@
 #include <uuid/uuid.h>
 #include <wordexp.h>
 
-char VDIR[256];
-
-// Most memory is managed by memory_region, two exceptions are the
-// global states fuse_root and entries_original_ics, which manages its own
-// resources.
-
-// A structure of the current root
-struct tree_node *fuse_root = NULL;
-// ics entries, keys are the filename as stored in ICS_DIR
-// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
-struct hashmap *entries_vdir = NULL;
-
 struct agenda_entry *
 copy_agenda_entry(const struct agenda_entry *src)
 {
@@ -61,938 +45,16 @@ free_agenda_entry(struct agenda_entry *entry)
 	return;
 }
 
-struct tree_node *
-get_node_by_uuid(memory_region *mreg, const char *target_uuid)
-{
-	char *filename_vdir = NULL;
-
-	// Filenames are uid.ics, so we levarage that
-	size_t wRes = rasprintf(mreg, &filename_vdir, "%s.ics", target_uuid);
-	assert(wRes != -1);
-
-	LOG("Looking for UID: '%s'", target_uuid);
-	LOG("Full filename: '%s'", filename_vdir);
-
-	struct tree_node *node = hashmap_get(entries_vdir, filename_vdir);
-	LOG("Found it: %b", node != NULL);
-
-	return node;
-}
-
-char *
-get_node_filename(memory_region *mreg, const struct tree_node *node)
-{
-	struct agenda_entry *entry = node->data;
-	if (entry == NULL)
-		return "";
-	return rstrdup(mreg, entry->filename);
-}
-
-struct tree_node *
-get_node_by_path(memory_region *mreg, const char *path)
-{
-	if (strcmp(path, "/") == 0) {
-		LOG("Is ROOT %s", path);
-		return fuse_root;
-	}
-
-	char *segments[255];
-	size_t count = split_path(mreg, path, segments);
-
-	LOG("Segments are defined");
-
-	struct tree_node *current = fuse_root;
-
-	for (size_t i = 0; i < count && current; ++i) {
-		const char *segment = segments[i];
-		struct tree_node *next = NULL;
-		LOG("Segment: %s", segment);
-
-		for (size_t j = 0; j < current->child_count; ++j) {
-
-			const char *child_name =
-			    get_node_filename(mreg, current->children[j]);
-			if (strcmp(child_name, segment) == 0) {
-
-				next = current->children[j];
-				break;
-			}
-		}
-		current = next;
-	}
-	LOG("Done");
-	return current;
-}
-
-bool
-node_is_directory(memory_region *mreg, const struct tree_node *node,
-		  icalcomponent *node_ics)
-{
-	if (is_root_node(node) || node_has_children(node)) {
-		return true;
-	}
-	else {
-		// When user creates a directory, a directory component is
-		// stored in the ics file. So even if a node has no children
-		// it can still be that it should be shown as a directory.
-		return is_directory_component(node_ics);
-	}
-}
-
-bool
-is_root_node(const struct tree_node *node)
-{
-	return !node->data;
-}
-
-struct agenda_entry *
-get_entry(const struct tree_node *node)
-{
-	assert(node->data);
-	return node->data;
-}
-
-char *
-get_vdir_filepath(memory_region *mreg, const struct tree_node *node)
-{
-	struct agenda_entry *entry = get_entry(node);
-	return append_path(mreg, VDIR, entry->filename_vdir);
-}
-
-size_t
-write_ical_file(memory_region *mreg, const struct tree_node *node,
-		icalcomponent *ic)
-{
-
-	// Always set last modified to the time we write
-	icalproperty *prop =
-	    icalcomponent_get_first_property(ic, ICAL_LASTMODIFIED_PROPERTY);
-	if (!prop) {
-		prop = icalproperty_new_lastmodified(get_ical_now());
-		icalcomponent_add_property(ic, prop);
-	}
-	else {
-		icalproperty_set_lastmodified(prop, get_ical_now());
-	}
-
-	icalcomponent_set_status(ic, ICAL_STATUS_FINAL);
-
-	const char *descr_prop = icalcomponent_get_description(ic);
-	if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
-		icalproperty *ical_descr_prop =
-		    icalcomponent_get_first_property(
-			icalcomponent_get_inner(ic), ICAL_DESCRIPTION_PROPERTY);
-		icalcomponent_remove_property(icalcomponent_get_inner(ic),
-					      ical_descr_prop);
-	}
-
-	char *ical_str = ricalcomponent_as_ical_string_r(mreg, ic);
-
-	int res = write_to_file(get_vdir_filepath(mreg, node), ical_str);
-
-	return res;
-}
-
-// Owner: ctx
-icalcomponent *
-get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n)
-{
-	if (is_root_node(n)) {
-		return NULL;
-	}
-
-	char *orig_path = get_vdir_filepath(mreg, n);
-	// TODO: Cache intermediate results within context
-	icalcomponent *ic = parse_ics_file(mreg, orig_path);
-	return ic;
-}
-
-int
-write_parent_child_components(memory_region *mreg,
-			      const struct tree_node *parent,
-			      icalcomponent *iparent,
-			      const struct tree_node *child,
-			      icalcomponent *ichild)
-{
-	LOG("Adding parent and child relation");
-	set_parent_child_relationship_to_component(iparent, ichild);
-	return write_ical_file(mreg, child, ichild);
-}
-
-// You also will need to update the ics components
-int
-move_node(struct tree_node *new_parent, struct tree_node *child)
-{
-	detach_tree_node(child);
-	return add_child(new_parent, child);
-}
-
-int
-update_node_filename(struct tree_node *node, const char *filename)
-{
-	struct agenda_entry *entry = node->data;
-	free(entry->filename);
-	entry->filename = xstrdup(filename);
-	return 0;
-}
-
-// Writes ics component summary to the filename of the tree_node
-int
-update_summary(memory_region *mreg, icalcomponent *ics,
-	       const struct tree_node *node)
-{
-	struct agenda_entry *e = get_entry(node);
-	icalcomponent_set_summary(ics, e->filename);
-	return write_ical_file(mreg, node, ics);
-}
-
-// Agenda entries are NOT stored in memory_region, and need to be freed
-// manually
-struct agenda_entry *
-parse_ics_to_agenda_entry(memory_region *mreg, const char *filepath)
-{
-	icalcomponent *component = parse_ics_file(mreg, filepath);
-	if (component == NULL) {
-		LOG("No component");
-		return NULL;
-	}
-	icalcomponent *journal =
-	    icalcomponent_get_first_real_component(component);
-
-	if (journal == NULL ||
-	    icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
-		LOG("Not journal component");
-		return NULL;
-	}
-	if (icalcomponent_get_summary(component) == NULL) {
-		LOG("No summary found");
-		return NULL;
-	}
-	struct agenda_entry *e = xmalloc(sizeof(struct agenda_entry));
-	region_register(mreg, e, (void *)free_agenda_entry);
-
-	e->filename = xstrdup(icalcomponent_get_summary(component));
-
-	e->filename_vdir = xstrdup(get_filename(filepath));
-
-	return e;
-}
-
-// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
-// to take ownership!
 struct agenda_entry *
-load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
-{
-	path *filepath = append_path(mreg, VDIR, filename);
-	LOG("Filepath is %s", filepath);
-
-	struct stat fileStat;
-	if (stat(filepath, &fileStat) == -1) {
-		LOG("Can not stat %s. skipping", filepath);
-		return NULL;
-	}
-
-	struct agenda_entry *new_entry =
-	    parse_ics_to_agenda_entry(mreg, filepath);
-	if (!new_entry) {
-		LOG("Could not parse entry");
-		return NULL;
-	}
-
-	// TODO: Load these from the original file dynamically instead.
-	new_entry->uid = fileStat.st_uid;
-	new_entry->gid = fileStat.st_gid;
-	new_entry->ino = fileStat.st_ino;
-
-	LOG("Parsed %s to file %s", new_entry->filename_vdir,
-	    new_entry->filename);
-	return new_entry;
-}
-
-void
-load_root_node_tree()
-{
-	entries_vdir = hashmap_new(NULL);
-	fuse_root = create_tree_node(NULL, NULL);
-	memory_region *mreg = create_region();
-	LOG("Loading journal entries from: %s\n", VDIR);
-
-	DIR *dir = opendir(VDIR);
-	if (!dir) {
-		perror("opendir");
-		return;
-	}
-
-	struct dirent *entry;
-	while ((entry = readdir(dir))) {
-		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
-			LOG("Loading %s", entry->d_name);
-			struct agenda_entry *new_entry =
-			    load_agenda_entry_from_ics_file(mreg,
-							    entry->d_name);
-
-			struct agenda_entry *owned_entry =
-			    copy_agenda_entry(new_entry);
-
-			if (!new_entry) {
-				LOG("Skipping entry %s", entry->d_name);
-				continue;
-			}
-
-			struct tree_node *new_node = create_tree_node(
-			    owned_entry, (void *)free_agenda_entry);
-
-			hashmap_insert(entries_vdir, new_entry->filename_vdir,
-				       new_node);
-
-			LOG("Inserted filename_vdir: %s, %s",
-			    owned_entry->filename, owned_entry->filename_vdir);
-		}
-	}
-	closedir(dir);
-	LOG("Set up directories according to parent-child");
-
-	size_t n_keys = 0;
-	char **keys = hashmap_get_keys(entries_vdir, &n_keys);
-	for (size_t i = 0; i < n_keys; i++) {
-		const char *filename = keys[i];
-		struct tree_node *child = hashmap_get(entries_vdir, filename);
-
-		LOG("Parsing %s", filename);
-		icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
-		LOG("Success");
-
-		const char *parent_uid = get_parent_uid(ic);
-		if (parent_uid) {
-			LOG("Has parent");
-
-			struct tree_node *parent =
-			    get_node_by_uuid(mreg, parent_uid);
-			if (parent) {
-				detach_tree_node(child);
-				add_child(parent, child);
-			}
-			else {
-				LOG("COULD NOT FIND PARENT NODE: %s",
-				    parent_uid);
-			}
-		}
-		else {
-			add_child(fuse_root, child);
-		}
-	}
-	LOG("Inserted %zu entries", n_keys);
-
-	for (int i = 0; i < n_keys; i++) {
-		free(keys[i]);
-	}
-	free(keys);
-	rfree_all(mreg);
-
-	LOG("Done");
-}
-
-size_t
-load_agendafs_environment(char *vdir_env)
-{
-	wordexp_t expanded;
-	if (wordexp(vdir_env, &expanded, 0) != 0 || expanded.we_wordc == 0) {
-		fprintf(stderr, "Failed to expand CALDAVFS_ICS_DIR\n");
-		wordfree(&expanded);
-		return -1;
-	}
-
-	char *expanded_path = expanded.we_wordv[0];
-
-	struct stat s;
-	if (stat(expanded_path, &s) == 0 && S_ISDIR(s.st_mode)) {
-		strlcpy(VDIR, expanded_path, sizeof(VDIR));
-		wordfree(&expanded);
-		return 0;
-	}
-
-	fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
-		expanded_path);
-	return -1;
-}
-
-static int
-append_category_to_memstream(FILE *memstream, const char *category,
-			     bool is_first_category)
+create_agenda_entry(memory_region *mreg, const char *filename,
+		    const char *filename_vdir)
 {
-	int res = 0;
-	if (!is_first_category) {
-		res = fputs(",", memstream);
-		assert(res != EOF);
-	}
-
-	res = fputs(category, memstream);
-	assert(res != EOF);
-	return 0;
-}
-
-void
-icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
-				 const char *buf, size_t size, off_t offset)
-{
-
-	const char *old_desc = icalcomponent_get_description(ic);
-	if (!old_desc)
-		old_desc = "";
-
-	char *new_desc = rstrins(mreg, old_desc, offset, buf, size);
-	icalcomponent_set_description(ic, new_desc);
-}
-
-// Returns comma separated list of categories for a file
-char *
-get_node_categories(memory_region *mreg, const struct tree_node *node)
-{
-	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
-	if (!component) {
-		return NULL;
-	}
-
-	FILE *memstream = NULL;
-	char *result_buffer = NULL;
-	region_register(mreg, result_buffer, free);
-	size_t buffer_size = 0;
-
-	memstream = open_memstream(&result_buffer, &buffer_size);
-
-	icalcomponent *inner = icalcomponent_get_inner(component);
-
-	icalproperty *catp =
-	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
-	bool first_category = true;
-	while (catp) {
-		const char *category = icalproperty_get_categories(catp);
-		append_category_to_memstream(memstream, category,
-					     first_category);
-		catp = icalcomponent_get_next_property(
-		    inner, ICAL_CATEGORIES_PROPERTY);
-
-		first_category = false;
-	}
-
-	size_t res = fclose(memstream);
-	assert(res != EOF);
-
-	return result_buffer;
-}
-
-int
-delete_node_categories(memory_region *mreg, const struct tree_node *node)
-{
-	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
-	if (!component) {
-		return 0;
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(component);
-
-	icalproperty *catp =
-	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
-	// Delete old categories
-	while (catp) {
-		icalcomponent_remove_property(inner, catp);
-
-		catp = icalcomponent_get_next_property(
-		    inner, ICAL_CATEGORIES_PROPERTY);
-	}
-
-	return write_ical_file(mreg, node, component);
-}
-
-int
-set_node_categories(memory_region *mreg, const struct tree_node *node,
-		    const char *new_categories, size_t s)
-{
-	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
-	if (!component) {
-		return 0;
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(component);
-
-	icalproperty *catp =
-	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
-	// Delete old categories
-	while (catp) {
-		icalcomponent_remove_property(inner, catp);
-
-		catp = icalcomponent_get_next_property(
-		    inner, ICAL_CATEGORIES_PROPERTY);
-	}
-
-	if (s == 0) {
-		return write_ical_file(mreg, node, component);
-	}
-
-	char *null_terminated_categories = rstrndup(mreg, new_categories, s);
-
-	icalproperty *new_cat_prop =
-	    icalproperty_new_categories(null_terminated_categories);
-	icalcomponent_add_property(inner, new_cat_prop);
-
-	return write_ical_file(mreg, node, component);
-}
-
-const char *
-get_node_class(memory_region *mreg, const struct tree_node *node)
-{
-	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
-	if (!component) {
-		return 0;
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(component);
-
-	const icalproperty *classp =
-	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
-	const enum icalproperty_class classv = icalproperty_get_class(classp);
-
-	if (classp) {
-		return format_ical_class(classv);
-	}
-
-	return "";
-}
-
-int
-delete_node_class(memory_region *mreg, const struct tree_node *node)
-{
-	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
-	if (!component) {
-		return 0;
-	}
-	icalcomponent *inner = icalcomponent_get_inner(component);
-	icalproperty *prop =
-	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
-	icalcomponent_remove_property(inner, prop);
-
-	return write_ical_file(mreg, node, component);
-}
-
-int
-set_node_class(memory_region *mreg, const struct tree_node *node,
-	       const icalproperty_class new_class)
-{
-	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
-	if (!component) {
-		return 0;
-	}
-
-	icalcomponent *inner = icalcomponent_get_inner(component);
-
-	icalproperty *classp =
-	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
-	icalcomponent_remove_property(inner, classp);
-
-	icalproperty *new_class_prop = icalproperty_new_class(new_class);
-	icalcomponent_add_property(inner, new_class_prop);
-
-	return write_ical_file(mreg, node, component);
-}
-
-uid_t
-get_node_uid(const struct tree_node *node)
-{
-	if (is_root_node(node)) {
-		return getuid();
-	}
-	return get_entry(node)->uid;
-}
-
-gid_t
-get_node_gid(const struct tree_node *node)
-{
-	if (is_root_node(node)) {
-		return getgid();
-	}
-	return get_entry(node)->gid;
-}
-ino_t
-get_node_ino(const struct tree_node *node)
-{
-	if (is_root_node(node)) {
-		return 0;
-	}
-	return get_entry(node)->ino;
-}
-
-int
-create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
-{
-	// TODO: We are mixing some responsibilities here.
-	// Any function that operates on the global state should not
-	// have memory_region as a parameter, as that makes it easier
-	// to mix up the contexts.
-	const char *entry_prefix = "/";
-	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
-		return -EINVAL;
-	}
-	const char *new_basename = get_filename(fuse_path);
-
-	struct agenda_entry *new_entry = xmalloc(sizeof(struct agenda_entry));
-	icalcomponent *new_component =
-	    create_vjournal_entry(mreg, new_basename);
-
-	new_entry->filename = xstrdup(new_basename);
-	if (asprintf(&new_entry->filename_vdir, "%s.ics",
-		     icalcomponent_get_uid(new_component)) == -1) {
-		LOG("Failed to create journal entry");
-		free_agenda_entry(new_entry);
-		return -ENOMEM;
-	}
-	LOG("Inserting vjournal directory");
-
-	struct tree_node *new_node =
-	    create_tree_node(new_entry, (void *)free_agenda_entry);
-
-	char *parent_path = get_parent_path(mreg, fuse_path);
-	LOG("Parent path is %s", parent_path);
-	struct tree_node *parent = get_node_by_path(mreg, parent_path);
-	icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
-
-	if (!parent) {
-		LOG("Parent does not exist");
-		return -ENOENT;
-	}
-	if (!is_root_node(parent) ||
-	    (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
-		LOG("Parent is not directory ");
-		return -ENOTDIR;
-	}
-
-	if (parent_ics) {
-		write_parent_child_components(mreg, parent, parent_ics,
-					      new_node, new_component);
-
-		add_child(parent, new_node);
-	}
-
-	hashmap_insert(entries_vdir, new_entry->filename_vdir, new_node);
-
-	if (write_ical_file(mreg, new_node, new_component) != 0) {
-		LOG("Write to entry failed");
-		hashmap_remove(entries_vdir, new_entry->filename_vdir);
-
-		free_tree(new_node);
-		return -EIO;
-	}
-
-	LOG("FILE CREATED");
-	return 0;
-}
-
-int
-create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path)
-{
-	const char *entry_prefix = "/";
-	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
-		return -EINVAL;
-	}
-	char *new_basename = strrchr(fuse_path, '/');
-	new_basename++;
-
-	struct agenda_entry *new_entry =
-	    xcalloc(1, sizeof(struct agenda_entry));
-	icalcomponent *new_component =
-	    create_vjournal_directory(mreg, new_basename);
-
-	new_entry->filename = xstrdup(new_basename);
-	if (asprintf(&new_entry->filename_vdir, "%s.ics",
-		     icalcomponent_get_uid(new_component)) == -1) {
-		LOG("Failed to create journal entry");
-		free_agenda_entry(new_entry);
-		return -ENOMEM;
-	}
-	LOG("Inserting vjournal directory");
-
-	struct tree_node *new_node =
-	    create_tree_node(new_entry, (void *)free_agenda_entry);
-
-	char *parent_path = get_parent_path(mreg, fuse_path);
-	LOG("Parent path is %s", parent_path);
-	struct tree_node *parent = get_node_by_path(mreg, parent_path);
-	icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
-
-	if (!parent) {
-		LOG("Parent does not exist");
-		return -ENOENT;
-	}
-	if (!is_root_node(parent) ||
-	    (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
-		LOG("Parent is not directory ");
-		return -ENOTDIR;
-	}
-
-	if (parent_ics) {
-		write_parent_child_components(mreg, parent, parent_ics,
-					      new_node, new_component);
-
-		add_child(parent, new_node);
-	}
-
-	hashmap_insert(entries_vdir, new_entry->filename_vdir, new_node);
-
-	if (write_ical_file(mreg, new_node, new_component) != 0) {
-		LOG("Write to entry failed");
-		hashmap_remove(entries_vdir, new_entry->filename_vdir);
-
-		free_tree(new_node);
-		return -EIO;
-	}
-
-	LOG("DIRECTORY CREATED");
-	return 0;
-}
-
-void
-update_or_create_fuse_entry_from_vdir(memory_region *mreg,
-				      const char *filepath_vdir)
-{
-	const char *filename_vdir = get_filename(filepath_vdir);
-	LOG("Filename original is %s", filename_vdir);
-
-	struct agenda_entry *updated_entry =
-	    load_agenda_entry_from_ics_file(mreg, filename_vdir);
-
-	struct agenda_entry *updated_entry_owned =
-	    copy_agenda_entry(updated_entry);
-
-	struct tree_node *new_or_updated_node =
-	    hashmap_get(entries_vdir, filename_vdir);
-
-	if (new_or_updated_node) {
-		LOG("Updating existing entry");
-		update_node_data(new_or_updated_node, updated_entry_owned);
-	}
-	else {
-		LOG("Creating new entry");
-		new_or_updated_node = create_tree_node(
-		    updated_entry_owned, (void *)free_agenda_entry);
-		hashmap_insert(entries_vdir, updated_entry_owned->filename_vdir,
-			       new_or_updated_node);
-	}
-
-	icalcomponent *entry_ics =
-	    get_icalcomponent_from_node(mreg, new_or_updated_node);
-
-	const char *new_parent_uid = get_parent_uid(entry_ics);
-
-	if (new_parent_uid) {
-		LOG("Has parent");
-		struct tree_node *new_parent =
-		    get_node_by_uuid(mreg, new_parent_uid);
-		move_node(new_parent, new_or_updated_node);
-	}
-	else {
-		LOG("Does not have parent, adding to root");
-		detach_tree_node(new_or_updated_node);
-		add_child(fuse_root, new_or_updated_node);
-	}
-
-	LOG("Tree updated");
-}
-
-int
-delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
-{
-	int res = 0;
-	struct tree_node *node = get_node_by_path(mreg, filepath);
-	if (!node) {
-		return -EIO;
-	}
-
-	icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
-	if (!node_is_directory(mreg, node, node_ics)) {
-		return -ENOTDIR;
-	}
-	if (node->child_count > 0) {
-		return -ENOTEMPTY;
-	}
-
-	char *filepath_original = get_vdir_filepath(mreg, node);
-	if (!filepath_original) {
-		LOG("Entry not found");
-		return -EIO;
-	}
-	LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
-	res = remove(filepath_original);
-	if (res != 0) {
-		LOG("Failed to delete file");
-		return -res;
-	}
-
-	hashmap_remove(entries_vdir, filepath_original);
-
-	detach_tree_node(node);
-	free_tree(node);
-	return res;
-}
-
-int
-do_agenda_rename(memory_region *mreg, const char *old, const char *new)
-{
-	char *new_copy = rstrdup(mreg, new);
-	const char *new_filename = get_filename(new_copy);
-
-	const char *old_filename = get_filename(old);
-	if (!old_filename)
-		return -EINVAL;
-
-	struct tree_node *new_parent_node =
-	    get_node_by_path(mreg, get_parent_path(mreg, new));
-
-	if (new_parent_node == NULL) {
-		return -EINVAL;
-	}
-
-	char *new_summary = rstrdup(mreg, new_filename);
-
-	// Remove everything after extension
-	// TODO: Store the extension as a separate field in the ics file
-	char *dot = strrchr(new_summary, '.');
-	if (dot) {
-		*dot = '\0';
-	}
-
-	LOG("Summary is now %s", new_summary);
-	struct tree_node *child_node = get_node_by_path(mreg, old);
-	assert(child_node);
-	icalcomponent *child_ics =
-	    get_icalcomponent_from_node(mreg, child_node);
-	assert(child_ics);
-
-	icalcomponent *new_parent_ics =
-	    get_icalcomponent_from_node(mreg, new_parent_node);
-
-	struct tree_node *old_parent_node = child_node->parent;
-	icalcomponent *old_parent_ics =
-	    get_icalcomponent_from_node(mreg, old_parent_node);
-
-	update_node_filename(child_node, new_filename);
-	update_summary(mreg, child_ics, child_node);
-
-	if (old_parent_node && old_parent_ics) {
-		remove_parent_child_relationship_from_component(old_parent_ics,
-								child_ics);
-	}
-
-	detach_tree_node(child_node);
-
-	if (new_parent_ics) {
-		move_node(new_parent_node, child_node);
-		write_parent_child_components(mreg, new_parent_node,
-					      new_parent_ics, child_node,
-					      child_ics);
-	}
-	else {
-		add_child(fuse_root, child_node);
-	}
-	return 0;
-}
-
-int
-delete_from_fuse_path(memory_region *mreg, const char *filepath)
-{
-	int res = 0;
-	struct tree_node *node = get_node_by_path(mreg, filepath);
-	if (!node) {
-		return -ENOENT;
-	}
-
-	icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
-	if (node_is_directory(mreg, node, node_ics)) {
-		return -EISDIR;
-	}
-	char *filepath_original = get_vdir_filepath(mreg, node);
-
-	LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
-	res = remove(filepath_original);
-	if (res == -1) {
-		return -EIO;
-	}
-
-	hashmap_remove(entries_vdir, filepath_original);
-	detach_tree_node(node);
-	free_tree(node);
-	return res;
-}
-
-char *
-get_node_path(memory_region *mreg, struct tree_node *node)
-{
-	if (!node->parent) {
-		return "/";
-	}
-
-	struct tree_node *curr = node;
-	char *result = NULL;
-	size_t result_len = 0;
-
-	FILE *stream = open_memstream(&result, &result_len);
-	assert(stream);
-
-	do {
-		if (!is_root_node(curr)) {
-			char *filename = get_node_filename(mreg, curr);
-			fprintf(stream, "/%s", filename);
-		}
-
-		curr = curr->parent;
-
-	} while (curr->parent);
-
-	fclose(stream);
-	if (result) {
-		region_register(mreg, result, free);
-	}
-
-	return result;
-}
-
-char *
-get_fuse_path_from_vdir(memory_region *mreg, const char *filepath)
-{
-	struct tree_node *node =
-	    hashmap_get(entries_vdir, get_filename(filepath));
-
-	if (node) {
-		return get_node_path(mreg, node);
-	}
-	return NULL;
-}
-
-int
-delete_from_vdir_path(memory_region *mreg, const char *filepath)
-{
-	// Strip path, validate just the new basename
-	const char *keyname = get_filename(filepath);
-	struct agenda_entry *entry = hashmap_get(entries_vdir, keyname);
-	if (!entry) {
-		return -EIO;
-	}
-
-	// TODO: Fix so it finds by actual UUID, not filepath
-	struct tree_node *node = get_node_by_uuid(mreg, filepath);
-
-	// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
-	// Instead, update the children so they have a new parent
-	// which is the parent node.
-	// Requires a change_parent function.
-	detach_tree_node(node);
-	free_tree(node);
-	hashmap_remove(entries_vdir, keyname);
-	return 0;
+	struct agenda_entry *copy = xmalloc(sizeof(struct agenda_entry));
+	copy->filename = xstrdup(filename);
+	copy->filename_vdir = xstrdup(filename_vdir);
+	// copy->gid = src->gid;
+	// copy->uid = src->uid;
+	// copy->ino = src->ino;
+	region_register(mreg, copy, (void *)free_agenda_entry);
+	return copy;
 }
diff --git a/agenda_entry.h b/agenda_entry.h
@@ -33,153 +33,14 @@ struct agenda_entry {
 	ino_t ino;
 };
 
-extern char VDIR[256];
-
-extern const char *IS_DIRECTORY_PROPERTY;
-
-// A structure of the current root
-extern struct tree_node *fuse_root;
-// ics entries, keys are the filename as stored in ICS_DIR
-// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
-extern struct hashmap *entries_vdir;
-
 struct agenda_entry *
 copy_agenda_entry(const struct agenda_entry *src);
 
 void
 free_agenda_entry(struct agenda_entry *entry);
 
-struct tree_node *
-get_node_by_uuid(memory_region *mreg, const char *target_uuid);
-
-char *
-get_node_filename(memory_region *mreg, const struct tree_node *node);
-
-struct tree_node *
-get_node_by_path(memory_region *mreg, const char *path);
-
-bool
-node_is_directory(memory_region *mreg, const struct tree_node *node,
-		  icalcomponent *node_ics);
-
-bool
-is_root_node(const struct tree_node *node);
-
-struct agenda_entry *
-get_entry(const struct tree_node *node);
-
-char *
-get_vdir_filepath(memory_region *mreg, const struct tree_node *node);
-
-// Owner: ctx
-icalcomponent *
-parse_ics_file(memory_region *mreg, const char *filename);
-
-icaltimetype
-get_ical_now();
-
-size_t
-write_ical_file(memory_region *mreg, const struct tree_node *node,
-		icalcomponent *ic);
-
-// Owner: ctx
-icalcomponent *
-get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n);
-
-int
-write_parent_child_components(memory_region *mreg,
-			      const struct tree_node *parent,
-			      icalcomponent *iparent,
-			      const struct tree_node *child,
-			      icalcomponent *ichild);
-
-// You also will need to update the ics components
-int
-move_node(struct tree_node *new_parent, struct tree_node *child);
-
-int
-update_node_filename(struct tree_node *node, const char *filename);
-
-// Writes ics component summary to the filename of the tree_node
-int
-update_summary(memory_region *mreg, icalcomponent *ics,
-	       const struct tree_node *node);
-
 struct agenda_entry *
-parse_ics_to_agenda_entry(memory_region *mreg, const char *filename);
-
-// Will be cleaned up by mreg
-struct agenda_entry *
-load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename);
-
-void
-load_root_node_tree();
-
-size_t
-load_agendafs_environment(char *);
-
-// Returns comma separated list of categories for a file
-char *
-get_node_categories(memory_region *mreg, const struct tree_node *node);
-
-int
-delete_node_categories(memory_region *mreg, const struct tree_node *node);
-
-int
-set_node_categories(memory_region *mreg, const struct tree_node *node,
-		    const char *new_categories, size_t s);
-
-const char *
-format_ical_class(const enum icalproperty_class iclass);
-
-const icalproperty_class
-parse_ical_class(const char *input);
-
-const char *
-get_node_class(memory_region *mreg, const struct tree_node *node);
-
-int
-delete_node_class(memory_region *mreg, const struct tree_node *node);
-
-int
-set_node_class(memory_region *mreg, const struct tree_node *node,
-	       const icalproperty_class new_class);
-
-char *
-create_new_unique_ics_uid(memory_region *mreg);
-ino_t
-get_node_ino(const struct tree_node *node);
-char *
-get_fuse_path_from_vdir(memory_region *mreg, const char *filepath);
-
-uid_t
-get_node_uid(const struct tree_node *node);
-
-gid_t
-get_node_gid(const struct tree_node *node);
-
-int
-create_entry_from_fuse(memory_region *mreg, const char *fuse_path);
-
-int
-create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path);
-
-void
-update_or_create_fuse_entry_from_vdir(memory_region *, const char *);
-
-int
-delete_dir_from_fuse_path(memory_region *mreg, const char *filepath);
-
-int
-do_agenda_rename(memory_region *, const char *, const char *);
-
-int
-delete_from_fuse_path(memory_region *mreg, const char *filepath);
-
-void
-icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
-				 const char *buf, size_t size, off_t offset);
-int
-delete_from_vdir_path(memory_region *mreg, const char *filepath);
+create_agenda_entry(memory_region *mreg, const char *filename,
+		    const char *filename_vdir);
 #endif // agenda_entry_h_INCLUDED
 
diff --git a/fuse_node.c b/fuse_node.c
@@ -0,0 +1,907 @@
+#include "agenda_entry.h"
+#include "fuse_node_store.h"
+#include "hashmap.h"
+#include "ical_extra.h"
+#include "mregion.h"
+#include "path.h"
+#include "tree.h"
+#include "util.h"
+#include <dirent.h>
+#include <libical/ical.h>
+#include <pthread.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <wordexp.h>
+
+struct tree_node *
+get_node_by_uuid(memory_region *mreg, const char *target_uuid)
+{
+	char *filename_vdir = NULL;
+
+	// Filenames are uid.ics, so we levarage that
+	size_t wRes = rasprintf(mreg, &filename_vdir, "%s.ics", target_uuid);
+	assert(wRes != -1);
+
+	LOG("Looking for UID: '%s'", target_uuid);
+	LOG("Full filename: '%s'", filename_vdir);
+
+	struct tree_node *node = get_fuse_node_from_vdir_name(filename_vdir);
+	LOG("Found it: %b", node != NULL);
+
+	return node;
+}
+
+char *
+get_node_filename(memory_region *mreg, const struct tree_node *node)
+{
+	struct agenda_entry *entry = node->data;
+	if (entry == NULL)
+		return "";
+	return rstrdup(mreg, entry->filename);
+}
+
+struct tree_node *
+get_node_by_path(memory_region *mreg, const char *path)
+{
+	if (strcmp(path, "/") == 0) {
+		LOG("Is ROOT %s", path);
+		return fuse_root;
+	}
+
+	char *segments[255];
+	size_t count = split_path(mreg, path, segments);
+
+	LOG("Segments are defined");
+
+	struct tree_node *current = fuse_root;
+
+	for (size_t i = 0; i < count && current; ++i) {
+		const char *segment = segments[i];
+		struct tree_node *next = NULL;
+		LOG("Segment: %s", segment);
+
+		for (size_t j = 0; j < current->child_count; ++j) {
+
+			const char *child_name =
+			    get_node_filename(mreg, current->children[j]);
+			if (strcmp(child_name, segment) == 0) {
+
+				next = current->children[j];
+				break;
+			}
+		}
+		current = next;
+	}
+	LOG("Done");
+	return current;
+}
+
+bool
+is_root_node(const struct tree_node *node)
+{
+	return !node->data;
+}
+
+bool
+node_is_directory(memory_region *mreg, const struct tree_node *node,
+		  icalcomponent *node_ics)
+{
+	if (is_root_node(node) || node_has_children(node)) {
+		return true;
+	}
+	else {
+		// When user creates a directory, a directory component is
+		// stored in the ics file. So even if a node has no children
+		// it can still be that it should be shown as a directory.
+		return is_directory_component(node_ics);
+	}
+}
+
+size_t
+write_ical_file(memory_region *mreg, const struct tree_node *node,
+		icalcomponent *ic)
+{
+
+	// Always set last modified to the time we write
+	icalproperty *prop =
+	    icalcomponent_get_first_property(ic, ICAL_LASTMODIFIED_PROPERTY);
+	if (!prop) {
+		prop = icalproperty_new_lastmodified(get_ical_now());
+		icalcomponent_add_property(ic, prop);
+	}
+	else {
+		icalproperty_set_lastmodified(prop, get_ical_now());
+	}
+
+	icalcomponent_set_status(ic, ICAL_STATUS_FINAL);
+
+	const char *descr_prop = icalcomponent_get_description(ic);
+	if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
+		icalproperty *ical_descr_prop =
+		    icalcomponent_get_first_property(
+			icalcomponent_get_inner(ic), ICAL_DESCRIPTION_PROPERTY);
+		icalcomponent_remove_property(icalcomponent_get_inner(ic),
+					      ical_descr_prop);
+	}
+
+	char *ical_str = ricalcomponent_as_ical_string_r(mreg, ic);
+
+	int res = write_to_file(get_vdir_filepath(mreg, node), ical_str);
+
+	return res;
+}
+
+// Owner: ctx
+icalcomponent *
+get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n)
+{
+	if (is_root_node(n)) {
+		return NULL;
+	}
+
+	char *orig_path = get_vdir_filepath(mreg, n);
+	// TODO: Cache intermediate results within context
+	icalcomponent *ic = parse_ics_file(mreg, orig_path);
+	return ic;
+}
+
+int
+write_parent_child_components(memory_region *mreg,
+			      const struct tree_node *parent,
+			      icalcomponent *iparent,
+			      const struct tree_node *child,
+			      icalcomponent *ichild)
+{
+	LOG("Adding parent and child relation");
+	set_parent_child_relationship_to_component(iparent, ichild);
+	return write_ical_file(mreg, child, ichild);
+}
+
+// Writes ics component summary to the filename of the tree_node
+int
+update_summary(memory_region *mreg, icalcomponent *ics,
+	       const struct tree_node *node)
+{
+	const struct agenda_entry *e = get_entry(node);
+	icalcomponent_set_summary(ics, e->filename);
+	return write_ical_file(mreg, node, ics);
+}
+
+struct agenda_entry *
+parse_ics_to_agenda_entry(memory_region *mreg, const char *vdir_filepath)
+{
+	icalcomponent *component = parse_ics_file(mreg, vdir_filepath);
+	if (component == NULL) {
+		LOG("No component");
+		return NULL;
+	}
+	icalcomponent *journal =
+	    icalcomponent_get_first_real_component(component);
+
+	if (journal == NULL ||
+	    icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
+		LOG("Not journal component");
+		return NULL;
+	}
+	if (icalcomponent_get_summary(component) == NULL) {
+		LOG("No summary found");
+		return NULL;
+	}
+	struct agenda_entry *e = xmalloc(sizeof(struct agenda_entry));
+	e->filename = xstrdup(icalcomponent_get_summary(component));
+	e->filename_vdir = xstrdup(get_filename(vdir_filepath));
+
+	region_register(mreg, e, (void *)free_agenda_entry);
+
+	return e;
+}
+
+// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
+// to take ownership!
+struct agenda_entry *
+load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
+{
+	path *filepath = append_path(mreg, VDIR, filename);
+	LOG("Filepath is %s", filepath);
+
+	struct stat fileStat;
+	if (stat(filepath, &fileStat) == -1) {
+		LOG("Can not stat %s. skipping", filepath);
+		return NULL;
+	}
+
+	struct agenda_entry *new_entry =
+	    parse_ics_to_agenda_entry(mreg, filepath);
+	if (!new_entry) {
+		LOG("Could not parse entry");
+		return NULL;
+	}
+
+	// TODO: Load these from the original file dynamically instead.
+	new_entry->uid = fileStat.st_uid;
+	new_entry->gid = fileStat.st_gid;
+	new_entry->ino = fileStat.st_ino;
+
+	LOG("Parsed %s to file %s", new_entry->filename_vdir,
+	    new_entry->filename);
+	return new_entry;
+}
+
+void
+load_root_node_tree()
+{
+	entries_vdir = hashmap_new(NULL);
+	fuse_root = create_tree_node(NULL, NULL);
+	memory_region *mreg = create_region();
+	LOG("Loading journal entries from: %s\n", VDIR);
+
+	DIR *dir = opendir(VDIR);
+	if (!dir) {
+		perror("opendir");
+		return;
+	}
+
+	struct dirent *entry;
+	while ((entry = readdir(dir))) {
+		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
+			LOG("Loading %s", entry->d_name);
+			struct agenda_entry *new_entry =
+			    load_agenda_entry_from_ics_file(mreg,
+							    entry->d_name);
+
+			create_fuse_node(new_entry);
+
+			LOG("Inserted filename_vdir: %s, %s",
+			    new_entry->filename, new_entry->filename_vdir);
+		}
+	}
+	closedir(dir);
+	LOG("Set up directories according to parent-child");
+
+	size_t n_keys = 0;
+	char **keys = hashmap_get_keys(entries_vdir, &n_keys);
+	for (size_t i = 0; i < n_keys; i++) {
+		const char *vdirname = keys[i];
+		struct tree_node *child =
+		    get_fuse_node_from_vdir_name(vdirname);
+
+		LOG("Parsing %s", filename);
+		icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
+		LOG("Success");
+
+		const char *parent_uid = get_parent_uid(ic);
+		if (parent_uid) {
+			LOG("Has parent");
+
+			struct tree_node *parent =
+			    get_node_by_uuid(mreg, parent_uid);
+			if (parent) {
+				move_node(parent, child);
+			}
+			else {
+				LOG("COULD NOT FIND PARENT NODE: %s",
+				    parent_uid);
+			}
+		}
+		else {
+			add_child(fuse_root, child);
+		}
+	}
+	LOG("Inserted %zu entries", n_keys);
+
+	hashmap_free_keys(keys, n_keys);
+	rfree_all(mreg);
+
+	LOG("Done");
+}
+
+size_t
+load_agendafs_environment(char *vdir_env)
+{
+	wordexp_t expanded;
+	if (wordexp(vdir_env, &expanded, 0) != 0 || expanded.we_wordc == 0) {
+		fprintf(stderr, "Failed to expand CALDAVFS_ICS_DIR\n");
+		wordfree(&expanded);
+		return -1;
+	}
+
+	char *expanded_path = expanded.we_wordv[0];
+
+	struct stat s;
+	if (stat(expanded_path, &s) == 0 && S_ISDIR(s.st_mode)) {
+		assert(strlen(expanded_path) < 256);
+		set_vdir(expanded_path);
+		wordfree(&expanded);
+		return 0;
+	}
+
+	fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
+		expanded_path);
+	return -1;
+}
+
+static int
+append_category_to_memstream(FILE *memstream, const char *category,
+			     bool is_first_category)
+{
+	int res = 0;
+	if (!is_first_category) {
+		res = fputs(",", memstream);
+		assert(res != EOF);
+	}
+
+	res = fputs(category, memstream);
+	assert(res != EOF);
+	return 0;
+}
+
+// Returns comma separated list of categories for a file
+char *
+get_node_categories(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return NULL;
+	}
+
+	FILE *memstream = NULL;
+	char *result_buffer = NULL;
+	region_register(mreg, result_buffer, free);
+	size_t buffer_size = 0;
+
+	memstream = open_memstream(&result_buffer, &buffer_size);
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *catp =
+	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+	bool first_category = true;
+	while (catp) {
+		const char *category = icalproperty_get_categories(catp);
+		append_category_to_memstream(memstream, category,
+					     first_category);
+		catp = icalcomponent_get_next_property(
+		    inner, ICAL_CATEGORIES_PROPERTY);
+
+		first_category = false;
+	}
+
+	size_t res = fclose(memstream);
+	assert(res != EOF);
+
+	return result_buffer;
+}
+
+int
+delete_node_categories(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *catp =
+	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+	// Delete old categories
+	while (catp) {
+		icalcomponent_remove_property(inner, catp);
+
+		catp = icalcomponent_get_next_property(
+		    inner, ICAL_CATEGORIES_PROPERTY);
+	}
+
+	return write_ical_file(mreg, node, component);
+}
+
+int
+set_node_categories(memory_region *mreg, const struct tree_node *node,
+		    const char *new_categories, size_t s)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *catp =
+	    icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+	// Delete old categories
+	while (catp) {
+		icalcomponent_remove_property(inner, catp);
+
+		catp = icalcomponent_get_next_property(
+		    inner, ICAL_CATEGORIES_PROPERTY);
+	}
+
+	if (s == 0) {
+		return write_ical_file(mreg, node, component);
+	}
+
+	char *null_terminated_categories = rstrndup(mreg, new_categories, s);
+
+	icalproperty *new_cat_prop =
+	    icalproperty_new_categories(null_terminated_categories);
+	icalcomponent_add_property(inner, new_cat_prop);
+
+	return write_ical_file(mreg, node, component);
+}
+
+const char *
+get_node_class(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	const icalproperty *classp =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	const enum icalproperty_class classv = icalproperty_get_class(classp);
+
+	if (classp) {
+		return format_ical_class(classv);
+	}
+
+	return "";
+}
+
+int
+delete_node_class(memory_region *mreg, const struct tree_node *node)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+	icalcomponent *inner = icalcomponent_get_inner(component);
+	icalproperty *prop =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	icalcomponent_remove_property(inner, prop);
+
+	return write_ical_file(mreg, node, component);
+}
+
+int
+set_node_class(memory_region *mreg, const struct tree_node *node,
+	       const icalproperty_class new_class)
+{
+	icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+	if (!component) {
+		return 0;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(component);
+
+	icalproperty *classp =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	icalcomponent_remove_property(inner, classp);
+
+	icalproperty *new_class_prop = icalproperty_new_class(new_class);
+	icalcomponent_add_property(inner, new_class_prop);
+
+	return write_ical_file(mreg, node, component);
+}
+
+uid_t
+get_node_uid(const struct tree_node *node)
+{
+	if (is_root_node(node)) {
+		return getuid();
+	}
+	return get_entry(node)->uid;
+}
+
+gid_t
+get_node_gid(const struct tree_node *node)
+{
+	if (is_root_node(node)) {
+		return getgid();
+	}
+	return get_entry(node)->gid;
+}
+ino_t
+get_node_ino(const struct tree_node *node)
+{
+	if (is_root_node(node)) {
+		return 0;
+	}
+	return get_entry(node)->ino;
+}
+
+int
+insert_fuse_node_to_path(memory_region *mreg, const char *fuse_path,
+			 struct tree_node *child_node, icalcomponent *child_ics)
+{
+	char *parent_path = get_parent_path(mreg, fuse_path);
+	LOG("Parent path is %s", parent_path);
+
+	struct tree_node *parent_node = get_node_by_path(mreg, parent_path);
+	if (!parent_node) {
+		LOG("Parent does not exist");
+		return -ENOENT;
+	}
+
+	icalcomponent *parent_ics =
+	    get_icalcomponent_from_node(mreg, parent_node);
+
+	if (!is_root_node(parent_node) ||
+	    (parent_ics && !node_is_directory(mreg, parent_node, parent_ics))) {
+		LOG("Parent is not a directory");
+		return -ENOTDIR;
+	}
+
+	if (parent_ics) {
+		write_parent_child_components(mreg, parent_node, parent_ics,
+					      child_node, child_ics);
+	}
+
+	add_child(parent_node, child_node);
+	return 0;
+}
+
+int
+create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
+{
+	// TODO: We are mixing some responsibilities here.
+	// Any function that operates on the global state should not
+	// have memory_region as a parameter, as that makes it easier
+	// to mix up the contexts.
+	const char *entry_prefix = "/";
+	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
+		return -EINVAL;
+	}
+	const char *new_filename = get_filename(fuse_path);
+	icalcomponent *new_component =
+	    create_vjournal_entry(mreg, new_filename);
+
+	char *new_filname_vdir = NULL;
+	rasprintf(mreg, &new_filname_vdir, "%s.ics",
+		  icalcomponent_get_uid(new_component));
+
+	struct agenda_entry *new_entry =
+	    create_agenda_entry(mreg, new_filename, new_filname_vdir);
+
+	LOG("Inserting vjournal directory");
+
+	struct tree_node *new_node = create_fuse_node(new_entry);
+
+	int status =
+	    insert_fuse_node_to_path(mreg, fuse_path, new_node, new_component);
+
+	if (status != 0) {
+		return -ENOENT;
+	}
+
+	if (write_ical_file(mreg, new_node, new_component) != 0) {
+		LOG("Write to entry failed");
+		hashmap_remove(entries_vdir, new_entry->filename_vdir);
+
+		free_tree(new_node);
+		return -EIO;
+	}
+
+	LOG("FILE CREATED");
+	return 0;
+}
+
+int
+create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path)
+{
+	const char *entry_prefix = "/";
+	if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
+		return -EINVAL;
+	}
+	char *new_basename = strrchr(fuse_path, '/');
+	new_basename++;
+
+	struct agenda_entry *new_entry =
+	    xcalloc(1, sizeof(struct agenda_entry));
+	icalcomponent *new_component =
+	    create_vjournal_directory(mreg, new_basename);
+
+	new_entry->filename = xstrdup(new_basename);
+	if (asprintf(&new_entry->filename_vdir, "%s.ics",
+		     icalcomponent_get_uid(new_component)) == -1) {
+		LOG("Failed to create journal entry");
+		free_agenda_entry(new_entry);
+		return -ENOMEM;
+	}
+	LOG("Inserting vjournal directory");
+
+	struct tree_node *new_node =
+	    create_tree_node(new_entry, (void *)free_agenda_entry);
+
+	char *parent_path = get_parent_path(mreg, fuse_path);
+	LOG("Parent path is %s", parent_path);
+	struct tree_node *parent = get_node_by_path(mreg, parent_path);
+	icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
+
+	if (!parent) {
+		LOG("Parent does not exist");
+		return -ENOENT;
+	}
+	if (!is_root_node(parent) ||
+	    (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
+		LOG("Parent is not directory ");
+		return -ENOTDIR;
+	}
+
+	if (parent_ics) {
+		write_parent_child_components(mreg, parent, parent_ics,
+					      new_node, new_component);
+
+		add_child(parent, new_node);
+	}
+
+	hashmap_insert(entries_vdir, new_entry->filename_vdir, new_node);
+
+	if (write_ical_file(mreg, new_node, new_component) != 0) {
+		LOG("Write to entry failed");
+		hashmap_remove(entries_vdir, new_entry->filename_vdir);
+
+		free_tree(new_node);
+		return -EIO;
+	}
+
+	LOG("DIRECTORY CREATED");
+	return 0;
+}
+
+void
+update_or_create_fuse_entry_from_vdir(memory_region *mreg,
+				      const char *filepath_vdir)
+{
+	const char *filename_vdir = get_filename(filepath_vdir);
+	LOG("Filename original is %s", filename_vdir);
+
+	struct agenda_entry *updated_entry =
+	    load_agenda_entry_from_ics_file(mreg, filename_vdir);
+
+	struct agenda_entry *updated_entry_owned =
+	    copy_agenda_entry(updated_entry);
+
+	struct tree_node *new_or_updated_node =
+	    hashmap_get(entries_vdir, filename_vdir);
+
+	if (new_or_updated_node) {
+		LOG("Updating existing entry");
+		update_node_data(new_or_updated_node, updated_entry_owned);
+	}
+	else {
+		LOG("Creating new entry");
+		new_or_updated_node = create_tree_node(
+		    updated_entry_owned, (void *)free_agenda_entry);
+		hashmap_insert(entries_vdir, updated_entry_owned->filename_vdir,
+			       new_or_updated_node);
+	}
+
+	icalcomponent *entry_ics =
+	    get_icalcomponent_from_node(mreg, new_or_updated_node);
+
+	const char *new_parent_uid = get_parent_uid(entry_ics);
+
+	if (new_parent_uid) {
+		LOG("Has parent");
+		struct tree_node *new_parent =
+		    get_node_by_uuid(mreg, new_parent_uid);
+		move_node(new_parent, new_or_updated_node);
+	}
+	else {
+		LOG("Does not have parent, adding to root");
+		detach_tree_node(new_or_updated_node);
+		add_child(fuse_root, new_or_updated_node);
+	}
+
+	LOG("Tree updated");
+}
+
+int
+delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
+{
+	int res = 0;
+	struct tree_node *node = get_node_by_path(mreg, filepath);
+	if (!node) {
+		return -EIO;
+	}
+
+	icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+	if (!node_is_directory(mreg, node, node_ics)) {
+		return -ENOTDIR;
+	}
+
+	if (node->child_count > 0) {
+		return -ENOTEMPTY;
+	}
+
+	char *filepath_original = get_vdir_filepath(mreg, node);
+	if (!filepath_original) {
+		LOG("Entry not found");
+		return -EIO;
+	}
+	LOG("Deleting file %s, located at %s", filepath, filepath_original);
+
+	res = remove(filepath_original);
+	if (res != 0) {
+		LOG("Failed to delete file");
+		return -res;
+	}
+
+	delete_fuse_node(node);
+
+	return res;
+}
+
+int
+do_agenda_rename(memory_region *mreg, const char *old, const char *new)
+{
+	char *new_copy = rstrdup(mreg, new);
+	const char *new_filename = get_filename(new_copy);
+
+	const char *old_filename = get_filename(old);
+	if (!old_filename)
+		return -EINVAL;
+
+	struct tree_node *new_parent_node =
+	    get_node_by_path(mreg, get_parent_path(mreg, new));
+
+	if (new_parent_node == NULL) {
+		return -EINVAL;
+	}
+
+	char *new_summary = rstrdup(mreg, new_filename);
+
+	// Remove everything after extension
+	// TODO: Store the extension as a separate field in the ics file
+	char *dot = strrchr(new_summary, '.');
+	if (dot) {
+		*dot = '\0';
+	}
+
+	LOG("Summary is now %s", new_summary);
+	struct tree_node *child_node = get_node_by_path(mreg, old);
+	assert(child_node);
+	icalcomponent *child_ics =
+	    get_icalcomponent_from_node(mreg, child_node);
+	assert(child_ics);
+
+	icalcomponent *new_parent_ics =
+	    get_icalcomponent_from_node(mreg, new_parent_node);
+
+	struct tree_node *old_parent_node = child_node->parent;
+	icalcomponent *old_parent_ics =
+	    get_icalcomponent_from_node(mreg, old_parent_node);
+
+	set_node_filename(child_node, new_filename);
+	update_summary(mreg, child_ics, child_node);
+
+	if (old_parent_node && old_parent_ics) {
+		remove_parent_child_relationship_from_component(old_parent_ics,
+								child_ics);
+	}
+
+	detach_tree_node(child_node);
+
+	if (new_parent_ics) {
+		move_node(new_parent_node, child_node);
+		write_parent_child_components(mreg, new_parent_node,
+					      new_parent_ics, child_node,
+					      child_ics);
+	}
+	else {
+		add_child(fuse_root, child_node);
+	}
+	return 0;
+}
+
+int
+delete_from_fuse_path(memory_region *mreg, const char *filepath)
+{
+	int res = 0;
+	struct tree_node *node = get_node_by_path(mreg, filepath);
+	if (!node) {
+		return -ENOENT;
+	}
+
+	icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+	if (node_is_directory(mreg, node, node_ics)) {
+		return -EISDIR;
+	}
+	char *filepath_original = get_vdir_filepath(mreg, node);
+
+	LOG("Deleting file %s, located at %s", filepath, filepath_original);
+
+	res = remove(filepath_original);
+	if (res == -1) {
+		return -EIO;
+	}
+
+	hashmap_remove(entries_vdir, filepath_original);
+	detach_tree_node(node);
+	free_tree(node);
+	return res;
+}
+
+char *
+get_node_path(memory_region *mreg, struct tree_node *node)
+{
+	if (!node->parent) {
+		return "/";
+	}
+
+	struct tree_node *curr = node;
+	char *result = NULL;
+	size_t result_len = 0;
+
+	FILE *stream = open_memstream(&result, &result_len);
+	assert(stream);
+
+	do {
+		if (!is_root_node(curr)) {
+			char *filename = get_node_filename(mreg, curr);
+			fprintf(stream, "/%s", filename);
+		}
+
+		curr = curr->parent;
+
+	} while (curr->parent);
+
+	fclose(stream);
+	if (result) {
+		region_register(mreg, result, free);
+	}
+
+	return result;
+}
+
+char *
+get_fuse_path_from_vdir(memory_region *mreg, const char *filepath)
+{
+	struct tree_node *node =
+	    hashmap_get(entries_vdir, get_filename(filepath));
+
+	if (node) {
+		return get_node_path(mreg, node);
+	}
+	return NULL;
+}
+
+int
+delete_from_vdir_path(memory_region *mreg, const char *filepath)
+{
+	// Strip path, validate just the new basename
+	const char *keyname = get_filename(filepath);
+	struct agenda_entry *entry = hashmap_get(entries_vdir, keyname);
+	if (!entry) {
+		return -EIO;
+	}
+
+	// TODO: Fix so it finds by actual UUID, not filepath
+	struct tree_node *node = get_node_by_uuid(mreg, filepath);
+
+	// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
+	// Instead, update the children so they have a new parent
+	// which is the parent node.
+	// Requires a change_parent function.
+	detach_tree_node(node);
+	free_tree(node);
+	hashmap_remove(entries_vdir, keyname);
+	return 0;
+}
diff --git a/fuse_node.h b/fuse_node.h
@@ -0,0 +1,140 @@
+#ifndef fuse_node_h_INCLUDED
+#define fuse_node_h_INCLUDED
+
+#include "agenda_entry.h"
+#include "fuse_node_store.h"
+#include "hashmap.h"
+#include "ical_extra.h"
+#include "mregion.h"
+#include "path.h"
+#include "tree.h"
+#include "util.h"
+#include <dirent.h>
+#include <libical/ical.h>
+#include <pthread.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <wordexp.h>
+
+struct tree_node *
+get_node_by_uuid(memory_region *mreg, const char *target_uuid);
+
+char *
+get_node_filename(memory_region *mreg, const struct tree_node *node);
+
+struct tree_node *
+get_node_by_path(memory_region *mreg, const char *path);
+
+bool
+is_root_node(const struct tree_node *node);
+
+bool
+node_is_directory(memory_region *mreg, const struct tree_node *node,
+		  icalcomponent *node_ics);
+
+size_t
+write_ical_file(memory_region *mreg, const struct tree_node *node,
+		icalcomponent *ic);
+
+// Owner: ctx
+icalcomponent *
+get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n);
+
+int
+write_parent_child_components(memory_region *mreg,
+			      const struct tree_node *parent,
+			      icalcomponent *iparent,
+			      const struct tree_node *child,
+			      icalcomponent *ichild);
+
+// Writes ics component summary to the filename of the tree_node
+int
+update_summary(memory_region *mreg, icalcomponent *ics,
+	       const struct tree_node *node);
+
+struct agenda_entry *
+parse_ics_to_agenda_entry(memory_region *mreg, const char *vdir_filepath);
+
+// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
+// to take ownership!
+struct agenda_entry *
+load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename);
+
+void
+load_root_node_tree();
+
+size_t
+load_agendafs_environment(char *vdir_env);
+
+// Returns comma separated list of categories for a file
+char *
+get_node_categories(memory_region *mreg, const struct tree_node *node);
+
+int
+delete_node_categories(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_categories(memory_region *mreg, const struct tree_node *node,
+		    const char *new_categories, size_t s);
+
+const char *
+get_node_class(memory_region *mreg, const struct tree_node *node);
+
+int
+delete_node_class(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_class(memory_region *mreg, const struct tree_node *node,
+	       const icalproperty_class new_class);
+
+uid_t
+get_node_uid(const struct tree_node *node);
+
+gid_t
+get_node_gid(const struct tree_node *node);
+
+ino_t
+get_node_ino(const struct tree_node *node);
+
+int
+insert_fuse_node_to_path(memory_region *mreg, const char *fuse_path,
+			 struct tree_node *child_node,
+			 icalcomponent *child_ics);
+
+int
+create_entry_from_fuse(memory_region *mreg, const char *fuse_path);
+
+int
+create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path);
+
+void
+update_or_create_fuse_entry_from_vdir(memory_region *mreg,
+				      const char *filepath_vdir);
+
+int
+delete_dir_from_fuse_path(memory_region *mreg, const char *filepath);
+
+int
+do_agenda_rename(memory_region *mreg, const char *, const char *);
+
+int
+delete_from_fuse_path(memory_region *mreg, const char *filepath);
+
+char *
+get_node_path(memory_region *mreg, struct tree_node *node);
+
+char *
+get_fuse_path_from_vdir(memory_region *mreg, const char *filepath);
+
+int
+delete_from_vdir_path(memory_region *mreg, const char *filepath);
+
+#endif // fuse_node_h_INCLUDED
diff --git a/fuse_node_store.c b/fuse_node_store.c
@@ -0,0 +1,74 @@
+#include "agenda_entry.h"
+#include "hashmap.h"
+#include "tree.h"
+#include "util.h"
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+char VDIR[256];
+
+// A structure of the current root
+struct tree_node *fuse_root = NULL;
+// ics entries, keys are the filename as stored in ICS_DIR
+// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
+struct hashmap *entries_vdir = NULL;
+
+// Copies vdir
+void
+set_vdir(const char *expanded_path)
+{
+	strlcpy(VDIR, expanded_path, sizeof(VDIR));
+}
+
+int
+set_node_filename(struct tree_node *node, const char *filename)
+{
+	struct agenda_entry *entry = node->data;
+	free(entry->filename);
+	entry->filename = xstrdup(filename);
+	return 0;
+}
+
+struct tree_node *
+get_fuse_node_from_vdir_name(const char *vdir_name)
+{
+	return hashmap_get(entries_vdir, vdir_name);
+}
+
+const struct agenda_entry *
+get_entry(const struct tree_node *node)
+{
+	assert(node->data);
+	return node->data;
+}
+
+char *
+get_vdir_filepath(memory_region *mreg, const struct tree_node *node)
+{
+	const struct agenda_entry *entry = get_entry(node);
+	return append_path(mreg, VDIR, entry->filename_vdir);
+}
+
+void
+delete_fuse_node(struct tree_node *node)
+{
+	const struct agenda_entry *e = get_entry(node);
+	hashmap_remove(entries_vdir, e->filename_vdir);
+	detach_tree_node(node);
+	free_tree(node);
+}
+
+// Copies agenda_entry
+struct tree_node *
+create_fuse_node(const struct agenda_entry *entry)
+{
+	struct agenda_entry *cpy = copy_agenda_entry(entry);
+
+	struct tree_node *new_node =
+	    create_tree_node(cpy, (void *)free_agenda_entry);
+
+	hashmap_insert(entries_vdir, cpy->filename_vdir, new_node);
+
+	return new_node;
+}
+
diff --git a/fuse_node_store.h b/fuse_node_store.h
@@ -0,0 +1,41 @@
+#ifndef fuse_node_store_h_INCLUDED
+#define fuse_node_store_h_INCLUDED
+#include "agenda_entry.h"
+#include "hashmap.h"
+#include "util.h"
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+extern char VDIR[256];
+
+// A structure of the current root
+extern struct tree_node *fuse_root;
+// ics entries, keys are the filename as stored in ICS_DIR
+// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
+extern struct hashmap *entries_vdir;
+
+void
+set_vdir(const char *expanded_path);
+
+int
+set_node_filename(struct tree_node *node, const char *filename);
+
+struct tree_node *
+get_fuse_node_from_vdir_name(const char *vdir_name);
+
+const struct agenda_entry *
+get_entry(const struct tree_node *node);
+
+char *
+get_vdir_filepath(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_filename(struct tree_node *node, const char *filename);
+
+struct tree_node *
+create_fuse_node(const struct agenda_entry *entry);
+
+void
+delete_fuse_node(struct tree_node *node);
+
+#endif // fuse_node_store_h_INCLUDED
diff --git a/ical_extra.c b/ical_extra.c
@@ -263,3 +263,17 @@ set_parent_child_relationship_to_component(icalcomponent *parent,
 		icalcomponent_add_property(inner, child_related_to_parent);
 	}
 }
+
+void
+icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
+				 const char *buf, size_t size, off_t offset)
+{
+
+	const char *old_desc = icalcomponent_get_description(ic);
+	if (!old_desc)
+		old_desc = "";
+
+	char *new_desc = rstrins(mreg, old_desc, offset, buf, size);
+	icalcomponent_set_description(ic, new_desc);
+}
+
diff --git a/ical_extra.h b/ical_extra.h
@@ -48,4 +48,7 @@ parse_ical_class(const char *input);
 const char *
 format_ical_class(const enum icalproperty_class iclass);
 
+void
+icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
+				 const char *buf, size_t size, off_t offset);
 #endif // ical_extra_h_INCLUDED
diff --git a/main.c b/main.c
@@ -1,8 +1,8 @@
 #define FUSE_USE_VERSION 31
 
-#include "agenda_entry.h"
 #include "fuse3/fuse_opt.h"
-#include "hashmap.h"
+#include "fuse_node.h"
+#include "fuse_node_store.h"
 #include "ical_extra.h"
 #include "mregion.h"
 #include "tree.h"
@@ -555,14 +555,15 @@ handle_vdir_event(struct inotify_event *event)
 	rasprintf(mreg, &full_path, "%s/%s", VDIR, event->name);
 
 	if (event->mask & IN_DELETE) {
+		LOG("IN_DELETE HOOK");
 		delete_from_vdir_path(mreg, full_path);
 	}
 	else if (event->mask & IN_MODIFY) {
-		LOG("RUNNING IN_MODIFY HOOK");
+		LOG("IN_MODIFY HOOK");
 		update_or_create_fuse_entry_from_vdir(mreg, full_path);
 	}
 	else if (event->mask & IN_CREATE) {
-		LOG("RUNNING IN_CREATE HOOK");
+		LOG("IN_CREATE HOOK");
 		update_or_create_fuse_entry_from_vdir(mreg, full_path);
 	}
 
diff --git a/tree.c b/tree.c
@@ -124,3 +124,10 @@ detach_tree_node(struct tree_node *node)
 	node->parent = NULL;
 	return true;
 }
+
+int
+move_node(struct tree_node *new_parent, struct tree_node *child)
+{
+	detach_tree_node(child);
+	return add_child(new_parent, child);
+}
diff --git a/tree.h b/tree.h
@@ -25,6 +25,9 @@ rcreate_tree_node(void *data, memory_region *m);
 bool
 node_has_children(const struct tree_node *node);
 
+int
+move_node(struct tree_node *new_parent, struct tree_node *child);
+
 void
 update_node_data(struct tree_node *node, void *data);