agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit 1aff4c96ef6d5f6deb6c34246f72b9aa544a34c2
parent 6a9c66a5235d7948ba935e1643b0ebc1be02880c
Author: Marc Coquand <marc@coquand.email>
Date:   Fri, 27 Jun 2025 10:59:26 +0100

*

Diffstat:
MREADME.md | 25+++++++++++++++++++------
Mjournal_entry.c | 145++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
2 files changed, 134 insertions(+), 36 deletions(-)
diff --git a/README.md b/README.md
@@ -44,17 +44,30 @@ These are not really intended to be edited by users.
 - [x] VJOURNAL - Now the first true journaling filesystem!
 	- [x] Create
 	- [x] Create remote
-	- [x] Delete locally
+	- [ ] Delete locally
 	- [x] Delete remote
 	- [x] Read
-	- [x] Rename
+	- [ ] Rename
 	- [x] Write
 	- [x] Set creation dates - not mandatory but supresses a few warnings
-	- [ ] Directory structure, children are VJOURNALs with parent as VJOURNAL
-	- [ ] Use DTSTART;VALUE=DATE: for journal to separate them from notes.
-- [ ] Categories 
+	- [x] Directory structure, children are VJOURNALs with parent as VJOURNAL
+- [ ] Categories
+- [ ] Parse files on demand instead of keeping everything in memory
+
+Right now we parse all files and keep in memory. Instead, we should parse them once, compute the file tree from that, and then just parse directories on demand with a cache.
+
+- [ ] Views
+
+A directory `.views` where representations of the caldavfs is kept.
+
+For example:
+- TODOs
+- Due todos soon
+- Agenda
+- DONE items
+
 - [ ] VTODO
-	- [ ] Embed in VJOURNAL using parent relationship
+	- [ ] Design.
 - [ ] VEVENTS
  	- [ ] Design. 
 - [ ] VALARM
diff --git a/journal_entry.c b/journal_entry.c
@@ -92,6 +92,17 @@ get_node_children(tree_node *node)
 	return entries;
 }
 
+void
+detach_node_and_entry(tree_node *node)
+{
+	tree_node *parent = node->parent;
+	if (!parent || !parent->data) {
+		return;
+	}
+	// TODO: Remove parent child relationship
+	detach_tree_node(node);
+}
+
 tree_node *
 get_node_by_uuid(const char *target_uuid)
 {
@@ -106,6 +117,14 @@ get_node_by_uuid(const char *target_uuid)
 	tree_node *node = hashmap_get(entries_original_ics, filename_original);
 	return node;
 }
+bool
+is_root_node(tree_node *node)
+{
+	if (!node->data) {
+		return true;
+	}
+	return false;
+}
 
 char **
 split_path(const char *path, size_t *count_out)
@@ -148,11 +167,59 @@ set_parent_child_relationship_to_component(icalcomponent *parent,
 
 	icalcomponent_add_property(parent, parent_related_to_child);
 }
+
+void
+remove_parent_child_relationship_from_component(icalcomponent *parent,
+						icalcomponent *child)
+{
+
+	const char *parent_uid = icalcomponent_get_uid(parent);
+	const char *child_uid = icalcomponent_get_uid(child);
+
+	// Remove from child: RELATED-TO:parent_uid;RELTYPE=PARENT
+	icalproperty *prop =
+	    icalcomponent_get_first_property(child, ICAL_RELATEDTO_PROPERTY);
+	while (prop != NULL) {
+		icalproperty *next = icalcomponent_get_next_property(
+		    child, ICAL_RELATEDTO_PROPERTY);
+		const char *value = icalproperty_get_relatedto(prop);
+		icalparameter *reltype = icalproperty_get_first_parameter(
+		    prop, ICAL_RELTYPE_PARAMETER);
+
+		if (value && strcmp(value, parent_uid) == 0 && reltype &&
+		    icalparameter_get_reltype(reltype) == ICAL_RELTYPE_PARENT) {
+			icalcomponent_remove_property(child, prop);
+			icalproperty_free(prop);
+		}
+
+		prop = next;
+	}
+
+	// Remove from parent: RELATED-TO:child_uid;RELTYPE=CHILD
+	prop =
+	    icalcomponent_get_first_property(parent, ICAL_RELATEDTO_PROPERTY);
+	while (prop != NULL) {
+		icalproperty *next = icalcomponent_get_next_property(
+		    parent, ICAL_RELATEDTO_PROPERTY);
+		const char *value = icalproperty_get_relatedto(prop);
+		icalparameter *reltype = icalproperty_get_first_parameter(
+		    prop, ICAL_RELTYPE_PARAMETER);
+
+		if (value && strcmp(value, child_uid) == 0 && reltype &&
+		    icalparameter_get_reltype(reltype) == ICAL_RELTYPE_CHILD) {
+			icalcomponent_remove_property(parent, prop);
+			icalproperty_free(prop);
+		}
+
+		prop = next;
+	}
+}
+
 void
 add_child_to_node(tree_node *parent, tree_node *child)
 {
 	// Root node, don't add relationship to component
-	if (parent->data == NULL) {
+	if (is_root_node(parent)) {
 		add_child(parent, child);
 		return;
 	}
@@ -395,17 +462,21 @@ parse_ics_to_journal_entry_component(const char *filename,
 	return 0;
 }
 
-char *
-time_string(icaltimetype t, const char *format)
+void
+move_node(tree_node *old_parent, tree_node *new_parent, tree_node *child)
 {
-
-	char *buffer = xmalloc(64);
-	time_t tme = icaltime_as_timet(t);
-	struct tm tm_info;
-	localtime_r(&tme, &tm_info);
-
-	strftime(buffer, 64, format, &tm_info);
-	return buffer;
+	struct journal_entry *child_entry = child->data;
+
+	// Not root node
+	if (!is_root_node(old_parent)) {
+		LOG("Removing old node");
+		struct journal_entry *old_parent_entry = old_parent->data;
+		remove_parent_child_relationship_from_component(
+		    old_parent_entry->component, child_entry->component);
+		write_entry_to_ical_file(old_parent_entry);
+	}
+	detach_tree_node(child);
+	add_child_to_node(new_parent, child);
 }
 
 // Returns -1 on failure, 0 on success
@@ -697,8 +768,16 @@ do_journal_entry_rename(const char *old, const char *new)
 	}
 	LOG("Summary is now %s", new_summary);
 	tree_node *entry_node = get_node_by_path(fuse_tree_root, old);
-	struct journal_entry *old_entry = entry_node->data;
+	if (!entry_node) {
+		free(new_copy);
+		free(new_summary);
+
+		return -EIO;
+	}
+
+	tree_node *old_parent_node = entry_node->parent;
 
+	struct journal_entry *old_entry = entry_node->data;
 	if (!old_entry) {
 		LOG("Entry not found '%s'", old);
 		free(new_copy);
@@ -713,7 +792,15 @@ do_journal_entry_rename(const char *old, const char *new)
 		return -EIO;
 	}
 
-	icalcomponent_set_summary(new_entry->component, new_summary);
+	char *parent_path = NULL;
+	get_parent_path(new, &parent_path);
+	LOG("PARENT PATH: %s", parent_path);
+	tree_node *new_parent_node =
+	    get_node_by_path(fuse_tree_root, parent_path);
+	free(parent_path);
+
+	move_node(old_parent_node, new_parent_node, entry_node);
+
 	// TODO: Filename must be unique
 	asprintf(&new_entry->filename, "%s", new_filename);
 	LOG("New filename is now %s", new_filename);
@@ -724,10 +811,11 @@ do_journal_entry_rename(const char *old, const char *new)
 	hashmap_insert(entries_original_ics, new_entry->filename_original,
 		       entry_node);
 
+	icalcomponent_set_summary(new_entry->component, new_summary);
 	entry_node->data = new_entry;
-	free_journal_entry(old_entry);
-
 	write_entry_to_ical_file(new_entry);
+
+	free_journal_entry(old_entry);
 	free(new_copy);
 
 	return 0;
@@ -860,24 +948,21 @@ create_entry_from_fuse(const char *fuse_path)
 
 	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);
-
-		if (!parent || !entry_is_directory(parent)) {
+	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);
 
-			return -ENOENT;
-		}
-		else if (!entry_is_directory(parent)) {
-			return -ENOTDIR;
-		}
-		add_child_to_node(parent, new_node);
+	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);