agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit 6a9c66a5235d7948ba935e1643b0ebc1be02880c
parent de45ebdb7c33f7439ef1c86929f40e2545d6bdae
Author: Marc Coquand <marc@coquand.email>
Date:   Thu, 26 Jun 2025 21:30:06 +0100

Bugfixes

Diffstat:
Mjournal_entry.c | 256++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mjournal_entry.h | 3+++
Mmain.c | 8+++-----
3 files changed, 194 insertions(+), 73 deletions(-)
diff --git a/journal_entry.c b/journal_entry.c
@@ -61,6 +61,30 @@ get_node_component(tree_node *node)
 	return entry->component;
 }
 
+int
+get_parent_path(const char *path, char **buffer)
+{
+	char *path_copy = strdup(path);
+
+	char *last_slash = strrchr(path_copy, '/');
+	if (last_slash == NULL) {
+		free(path_copy);
+		return 0;
+	}
+
+	// If they are the same, it means the parent is root
+	if (strcmp(last_slash, path_copy) == 0) {
+		int result = asprintf(buffer, "/");
+		free(path_copy);
+		return result;
+	}
+	*last_slash = '\0';
+	int result = asprintf(buffer, "%s", path_copy);
+	free(path_copy);
+
+	return result;
+}
+
 tree_node **
 get_node_children(tree_node *node)
 {
@@ -68,6 +92,21 @@ get_node_children(tree_node *node)
 	return entries;
 }
 
+tree_node *
+get_node_by_uuid(const char *target_uuid)
+{
+	char *filename_original = NULL;
+
+	// Filenames are uid.ics, so we levarage that
+	if (asprintf(&filename_original, "%s.ics", target_uuid) == 0) {
+		LOG("EMEM");
+		exit(1);
+	}
+
+	tree_node *node = hashmap_get(entries_original_ics, filename_original);
+	return node;
+}
+
 char **
 split_path(const char *path, size_t *count_out)
 {
@@ -91,6 +130,45 @@ split_path(const char *path, size_t *count_out)
 }
 
 void
+set_parent_child_relationship_to_component(icalcomponent *parent,
+					   icalcomponent *child)
+{
+	icalproperty *child_related_to_parent =
+	    icalproperty_new_relatedto(icalcomponent_get_uid(parent));
+	icalparameter *reltype_child =
+	    icalparameter_new_reltype(ICAL_RELTYPE_PARENT);
+	icalproperty_add_parameter(child_related_to_parent, reltype_child);
+	icalcomponent_add_property(child, child_related_to_parent);
+
+	icalproperty *parent_related_to_child =
+	    icalproperty_new_relatedto(icalcomponent_get_uid(child));
+	icalparameter *reltype_parent =
+	    icalparameter_new_reltype(ICAL_RELTYPE_CHILD);
+	icalproperty_add_parameter(parent_related_to_child, reltype_parent);
+
+	icalcomponent_add_property(parent, parent_related_to_child);
+}
+void
+add_child_to_node(tree_node *parent, tree_node *child)
+{
+	// Root node, don't add relationship to component
+	if (parent->data == NULL) {
+		add_child(parent, child);
+		return;
+	}
+	else {
+		struct journal_entry *p_entry = parent->data;
+		struct journal_entry *c_entry = child->data;
+		set_parent_child_relationship_to_component(p_entry->component,
+							   c_entry->component);
+		write_entry_to_ical_file(p_entry);
+		write_entry_to_ical_file(c_entry);
+
+		add_child(parent, child);
+	}
+}
+
+void
 free_segments(char **segments, size_t count)
 {
 	for (size_t i = 0; i < count; ++i)
@@ -99,21 +177,28 @@ free_segments(char **segments, size_t count)
 }
 
 struct tree_node *
-find_node_by_path(struct tree_node *root, const char *path)
+get_node_by_path(struct tree_node *root, const char *path)
 {
-	if (!root || !path || *path == '\0')
+	if (!path)
 		return NULL;
 
+	if (strcmp(path, "/") == 0) {
+		LOG("Is ROOT %s", path);
+		return fuse_tree_root;
+	}
+
 	size_t count;
 	char **segments = split_path(path, &count);
 	if (!segments)
-		return NULL;
+		return fuse_tree_root;
+	LOG("Segments are defined");
 
 	tree_node *current = root;
 
 	for (size_t i = 0; i < count && current; ++i) {
 		const char *segment = segments[i];
 		tree_node *next = NULL;
+		LOG("Segment: %s", segment);
 
 		for (size_t j = 0; j < current->child_count; ++j) {
 			const char *child_name =
@@ -177,7 +262,7 @@ get_entry_from_fuse_path(const char *path)
 		LOG("Invalid path: '%s'\n", path ? path : "NULL");
 		return NULL;
 	}
-	tree_node *node = find_node_by_path(fuse_tree_root, path);
+	tree_node *node = get_node_by_path(fuse_tree_root, path);
 
 	if (!node || !node->data) {
 		LOG("No entry found for key: '%s'\n", path);
@@ -205,7 +290,7 @@ is_directory_component(icalcomponent *component)
 
 		const char *reltype =
 		    icalproperty_get_parameter_as_string(prop, "RELTYPE");
-		if (reltype && strcasecmp(reltype, "PARENT") == 0) {
+		if (reltype && strcasecmp(reltype, "CHILD") == 0) {
 			return true;
 		}
 	}
@@ -347,7 +432,6 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
 
 	// Make sure filename, which happens when
 	// Two entries are created on the same minute
-	// TODO: Allow custom fileformats
 	// TODO: Check for duplicates
 	if (is_directory_component(entry->component)) {
 		if (asprintf(&entry->filename, "%s",
@@ -357,6 +441,9 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
 			return -1;
 		};
 	}
+
+	// TODO: Enable custom fileformats by storing them in a
+	// X-CALDAVFS-EXTENSION format
 	else if (asprintf(&entry->filename, "%s.txt",
 			  icalcomponent_get_summary(entry->component)) == -1) {
 		LOG("Failed to write filename");
@@ -368,10 +455,25 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
 	return 0;
 }
 
-// Initializes
-// entries_original_key
-// entries_fuse_key
-// Should only be run once
+const char *
+get_parent_uid(icalcomponent *component)
+{
+	for (icalproperty *prop = icalcomponent_get_first_property(
+		 component, ICAL_RELATEDTO_PROPERTY);
+	     prop != NULL; prop = icalcomponent_get_next_property(
+			       component, ICAL_RELATEDTO_PROPERTY)) {
+		const char *reltype =
+		    icalproperty_get_parameter_as_string(prop, "RELTYPE");
+		if (reltype && strcasecmp(reltype, "PARENT") == 0) {
+			return icalproperty_get_value_as_string(prop);
+		}
+	}
+
+	return NULL;
+}
+
+// Initializes values
+// Should only be run once at the start
 void
 load_journal_entries()
 {
@@ -396,6 +498,7 @@ load_journal_entries()
 	}
 
 	struct dirent *entry;
+	// First pass: Store everything in root tree, fill up hashmap
 	while ((entry = readdir(dir))) {
 		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
 			struct journal_entry *new_entry =
@@ -408,13 +511,12 @@ load_journal_entries()
 				continue;
 			}
 
-			// TODO: Check for duplicate
 			tree_node *new_node = create_file_node(new_entry);
 
 			if (add_child(fuse_tree_root, new_node) != 0 ||
 			    hashmap_insert(entries_original_ics,
 					   new_entry->filename_original,
-					   new_entry) != 0) {
+					   new_node) != 0) {
 				LOG("Failed to insert entry");
 				free_journal_entry(new_entry);
 				continue;
@@ -422,6 +524,28 @@ load_journal_entries()
 		}
 	}
 	closedir(dir);
+	LOG("Setting up directories");
+
+	// Second pass, reattach according to parent-child
+	for (size_t i = 0; i < fuse_tree_root->child_count; i++) {
+		tree_node *child = fuse_tree_root->children[i];
+		struct journal_entry *entry = child->data;
+
+		const char *parent_uid = get_parent_uid(entry->component);
+		if (parent_uid) {
+			tree_node *parent = get_node_by_uuid(parent_uid);
+			if (parent) {
+				detach_tree_node(child);
+				add_child(parent, child);
+			}
+			else {
+				LOG("COULD NOT FIND PARENT NODE: %s",
+				    parent_uid);
+			}
+		}
+	}
+	LOG("Done");
+	// TODO: Handle duplicates by changing file name to .1, .2 etc.
 }
 
 size_t
@@ -572,7 +696,7 @@ do_journal_entry_rename(const char *old, const char *new)
 		*dot = '\0';
 	}
 	LOG("Summary is now %s", new_summary);
-	tree_node *entry_node = find_node_by_path(fuse_tree_root, old);
+	tree_node *entry_node = get_node_by_path(fuse_tree_root, old);
 	struct journal_entry *old_entry = entry_node->data;
 
 	if (!old_entry) {
@@ -598,7 +722,7 @@ do_journal_entry_rename(const char *old, const char *new)
 
 	LOG("Inserting %s", new_entry->filename_original);
 	hashmap_insert(entries_original_ics, new_entry->filename_original,
-		       new_entry);
+		       entry_node);
 
 	entry_node->data = new_entry;
 	free_journal_entry(old_entry);
@@ -704,28 +828,6 @@ match_by_uid(icalcomponent *component, const char *target_uuid)
 	return false;
 }
 
-// TODO: Optimize
-tree_node *
-find_by_component_uuid(tree_node *root, const char *target_uuid)
-{
-	if (!root)
-		return NULL;
-
-	struct journal_entry *entry = root->data;
-	if (entry && entry->component &&
-	    match_by_uid(entry->component, target_uuid)) {
-		return root;
-	}
-
-	for (size_t i = 0; i < root->child_count; ++i) {
-		tree_node *found =
-		    find_by_component_uuid(root->children[i], target_uuid);
-		if (found)
-			return found;
-	}
-	return NULL;
-}
-
 // TODO: Handle duplicates in filename
 int
 create_entry_from_fuse(const char *fuse_path)
@@ -756,24 +858,34 @@ create_entry_from_fuse(const char *fuse_path)
 	}
 	LOG("Inserting vjournal entry");
 
-	hashmap_insert(entries_original_ics, new_entry->filename_original,
-		       new_entry);
+	tree_node *new_node = create_file_node(new_entry);
+	char *parent_path = NULL;
+	if (get_parent_path(fuse_path, &parent_path) == 0) {
+		add_child_to_node(fuse_tree_root, new_node);
+	}
+	else {
+		tree_node *parent =
+		    get_node_by_path(fuse_tree_root, parent_path);
+		free(parent_path);
 
-	add_child(fuse_tree_root, create_file_node(new_entry));
+		if (!parent || !entry_is_directory(parent)) {
 
-	// Write back to the original
-	char *filepath = get_original_filepath(new_entry);
-	LOG("Got original filepath %s. From %s, and %s", filepath,
-	    new_entry->filename_original, new_entry->filename);
+			return -ENOENT;
+		}
+		else if (!entry_is_directory(parent)) {
+			return -ENOTDIR;
+		}
+		add_child_to_node(parent, new_node);
+	}
+
+	hashmap_insert(entries_original_ics, new_entry->filename_original,
+		       new_node);
 
 	if (write_entry_to_ical_file(new_entry) != 0) {
 		LOG("Write to entry failed");
-		free(filepath);
 		return -EIO;
 	}
 
-	free(filepath);
-
 	LOG("File updated");
 	return 0;
 }
@@ -804,26 +916,33 @@ create_directory_from_fuse_path(const char *fuse_path)
 	}
 	LOG("Inserting vjournal directory");
 
-	hashmap_insert(entries_original_ics, new_entry->filename_original,
-		       new_entry);
+	tree_node *new_node = create_file_node(new_entry);
 
-	// TODO: Subdirectory
-	add_child(fuse_tree_root, create_file_node(new_entry));
+	char *parent_path = NULL;
+	get_parent_path(fuse_path, &parent_path);
+	LOG("Parent path is %s", parent_path);
+	tree_node *parent = get_node_by_path(fuse_tree_root, parent_path);
+	free(parent_path);
 
-	// Write back to the original
-	char *filepath = get_original_filepath(new_entry);
-	LOG("Got original filepath %s. From %s, and %s", filepath,
-	    new_entry->filename_original, new_entry->filename);
+	if (!parent) {
+		LOG("Parent is not node");
+		return -ENOENT;
+	}
+	if (!entry_is_directory(parent)) {
+		LOG("Parent is not directory ");
+
+		return -ENOTDIR;
+	}
+	add_child_to_node(parent, new_node);
+	hashmap_insert(entries_original_ics, new_entry->filename_original,
+		       new_node);
 
 	if (write_entry_to_ical_file(new_entry) != 0) {
 		LOG("Write to entry failed");
-		free(filepath);
 		return -EIO;
 	}
 
-	free(filepath);
-
-	LOG("File updated");
+	LOG("DIRECTORY CREATED");
 	return 0;
 }
 
@@ -846,20 +965,21 @@ update_or_create_fuse_entry_from_original(const char *filename)
 	if (!target_uuid) {
 		return;
 	}
-	hashmap_insert(entries_original_ics, new_entry->filename_original,
-		       new_entry);
 
 	// TODO: Handle directories
-	tree_node *existing_node =
-	    find_by_component_uuid(fuse_tree_root, target_uuid);
+	tree_node *existing_node = get_node_by_uuid(target_uuid);
 
 	// TODO: Enable subdirectories
 	if (existing_node) {
 		update_node_data(existing_node, new_entry);
 	}
 	else {
-
-		add_child(fuse_tree_root, create_file_node(new_entry));
+		tree_node *new_node = create_file_node(new_entry);
+		// TODO: Look for child-parent relationship and update
+		// accordingly
+		add_child(fuse_tree_root, new_node);
+		hashmap_insert(entries_original_ics,
+			       new_entry->filename_original, new_node);
 	}
 
 	LOG("File updated");
@@ -870,12 +990,11 @@ int
 delete_from_fuse_path(const char *filepath)
 {
 	int res = 0;
-	tree_node *node = find_node_by_path(fuse_tree_root, filepath);
+	tree_node *node = get_node_by_path(fuse_tree_root, filepath);
 	if (!node) {
 		return -EIO;
 	}
 	struct journal_entry *entry = get_entry(node);
-	const char *entry_uuid = icalcomponent_get_uid(entry->component);
 	char *filepath_original = get_original_filepath(entry);
 	if (!filepath_original) {
 		LOG("Entry not found");
@@ -885,7 +1004,7 @@ delete_from_fuse_path(const char *filepath)
 
 	res = remove(filepath);
 
-	hashmap_remove(entries_original_ics, entry_uuid);
+	hashmap_remove(entries_original_ics, filepath_original);
 
 	// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
 	// Instead, update the children so they have a new parent
@@ -908,7 +1027,8 @@ update_delete_from_original_path(const char *filepath)
 		return -EIO;
 	}
 
-	tree_node *node = find_by_component_uuid(fuse_tree_root, filepath);
+	// TODO: Fix so it finds by actual UUID, not filepath
+	tree_node *node = get_node_by_uuid(filepath);
 
 	// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
 	// Instead, update the children so they have a new parent
diff --git a/journal_entry.h b/journal_entry.h
@@ -127,6 +127,9 @@ get_last_modified(struct journal_entry *entry);
 int
 build_today_hashmap();
 
+struct tree_node *
+get_node_by_path(struct tree_node *root, const char *path);
+
 int
 build_notes_hashmap();
 
diff --git a/main.c b/main.c
@@ -1,9 +1,8 @@
-#include "tree.h"
-#include <cerrno>
 #define FUSE_USE_VERSION 31
 
 #include "hashmap.h"
 #include "journal_entry.h"
+#include "tree.h"
 #include "util.h"
 #include <dirent.h>
 #include <errno.h>
@@ -43,8 +42,7 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 		goto unlock_and_return;
 	}
 
-	// TODO: Handle directories
-	tree_node *node = find_node_by_path(fuse_tree_root, path);
+	tree_node *node = get_node_by_path(fuse_tree_root, path);
 	if (!node) {
 		LOG("Entry not found");
 		ret_code = -ENOENT;
@@ -118,7 +116,7 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
 		}
 	}
 	else {
-		tree_node *node = find_node_by_path(fuse_tree_root, path);
+		tree_node *node = get_node_by_path(fuse_tree_root, path);
 		if (node) {
 			for (size_t i = 0; i < node->child_count; i++) {
 				tree_node *child = node->children[i];