agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit bf02536a0f7f28bba661a5cfead7e694bce03325
parent 702aba8c32d11f478b35e6996138ed8bd8b7811b
Author: Marc Coquand <marc@coquand.email>
Date:   Sat, 26 Jul 2025 12:02:49 +0200

Refactor and code cleanup

Diffstat:
Magenda_entry.c | 12+++++++++---
Magenda_entry.h | 3---
Mfuse_node.c | 190++++++++++++++++++++++++++++---------------------------------------------------
Mfuse_node.h | 11+++--------
Mfuse_node_store.c | 16+++++++++++++++-
Mfuse_node_store.h | 3+++
Mmain.c | 43+++----------------------------------------
Mpath.c | 9+++++++++
Mpath.h | 3+++
9 files changed, 112 insertions(+), 178 deletions(-)
diff --git a/agenda_entry.c b/agenda_entry.c
@@ -26,9 +26,6 @@ copy_agenda_entry(const struct agenda_entry *src)
 	struct agenda_entry *copy = xmalloc(sizeof(struct agenda_entry));
 	copy->filename = xstrdup(src->filename);
 	copy->filename_vdir = xstrdup(src->filename_vdir);
-	copy->gid = src->gid;
-	copy->uid = src->uid;
-	copy->ino = src->ino;
 	return copy;
 }
 
@@ -45,6 +42,14 @@ free_agenda_entry(struct agenda_entry *entry)
 	return;
 }
 
+// Frees old filename and copies over the new
+void
+set_filename(struct agenda_entry *e, const char *new)
+{
+	free(e->filename);
+	e->filename = xstrdup(new);
+}
+
 struct agenda_entry *
 create_agenda_entry(memory_region *mreg, const char *filename,
 		    const char *filename_vdir)
@@ -52,6 +57,7 @@ create_agenda_entry(memory_region *mreg, const char *filename,
 	struct agenda_entry *copy = xmalloc(sizeof(struct agenda_entry));
 	copy->filename = xstrdup(filename);
 	copy->filename_vdir = xstrdup(filename_vdir);
+	// TODO: These should be read from icalcomponent instead
 	// copy->gid = src->gid;
 	// copy->uid = src->uid;
 	// copy->ino = src->ino;
diff --git a/agenda_entry.h b/agenda_entry.h
@@ -28,9 +28,6 @@ struct agenda_entry {
 	// filename_original is the relative path to ICS_DIR
 	// I.E. 910319208nrao19p.ics
 	char *filename_vdir;
-	uid_t uid;
-	gid_t gid;
-	ino_t ino;
 };
 
 struct agenda_entry *
diff --git a/fuse_node.c b/fuse_node.c
@@ -204,8 +204,7 @@ parse_ics_to_agenda_entry(memory_region *mreg, const char *vdir_filepath)
 	return e;
 }
 
-// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
-// to take ownership!
+// Owner: memory_region
 struct agenda_entry *
 load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
 {
@@ -225,11 +224,6 @@ load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
 		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;
@@ -256,11 +250,15 @@ load_root_node_tree()
 			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);
+			if (new_entry) {
+				create_fuse_node(new_entry);
+				LOG("Inserted filename_vdir: %s, %s",
+				    new_entry->filename,
+				    new_entry->filename_vdir);
+			}
+			else {
+				LOG("Could not load entry. Skipping");
+			}
 		}
 	}
 	closedir(dir);
@@ -273,7 +271,7 @@ load_root_node_tree()
 		struct tree_node *child =
 		    get_fuse_node_from_vdir_name(vdirname);
 
-		LOG("Parsing %s", filename);
+		LOG("Parsing %s", vdirname);
 		icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
 		LOG("Success");
 
@@ -500,30 +498,35 @@ set_node_class(memory_region *mreg, const struct tree_node *node,
 	return write_ical_file(mreg, node, component);
 }
 
-uid_t
-get_node_uid(const struct tree_node *node)
+struct stat
+get_node_stat(memory_region *mreg, const struct tree_node *node,
+	      icalcomponent *node_component)
 {
 	if (is_root_node(node)) {
-		return getuid();
+		struct stat st = {0};
+		st.st_gid = getgid();
+		st.st_uid = getuid();
+		st.st_ino = 0;
+		return st;
 	}
-	return get_entry(node)->uid;
-}
+	else {
+		struct stat vdir_stat = {0};
+		char *vdir_path = get_vdir_filepath(mreg, node);
+		int res = stat(vdir_path, &vdir_stat);
+		assert(res == 0);
+		vdir_stat.st_size =
+		    (off_t)get_entry_content_size(node_component);
+		if (node_is_directory(mreg, node, node_component)) {
+			vdir_stat.st_mode = S_IFDIR | 0444;
+			vdir_stat.st_nlink = 2;
+		}
+		else {
+			vdir_stat.st_mode = S_IFREG | 0774;
+			vdir_stat.st_nlink = 1;
+		}
 
-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 vdir_stat;
 	}
-	return get_entry(node)->ino;
 }
 
 int
@@ -592,9 +595,7 @@ create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
 
 	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);
+		delete_fuse_node(new_node);
 		return -EIO;
 	}
 
@@ -605,59 +606,36 @@ create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
 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++;
+	const char *new_filename = get_filename(fuse_path);
+	icalcomponent *new_component =
+	    create_vjournal_directory(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 =
-	    xcalloc(1, sizeof(struct agenda_entry));
-	icalcomponent *new_component =
-	    create_vjournal_directory(mreg, new_basename);
+	    create_agenda_entry(mreg, new_filename, new_filname_vdir);
 
-	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);
+	struct tree_node *new_node = create_fuse_node(new_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);
+	int status =
+	    insert_fuse_node_to_path(mreg, fuse_path, new_node, new_component);
 
-	if (!parent) {
-		LOG("Parent does not exist");
+	if (status != 0) {
 		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);
+		delete_fuse_node(new_node);
 		return -EIO;
 	}
 
@@ -665,7 +643,7 @@ create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path)
 	return 0;
 }
 
-void
+int
 update_or_create_fuse_entry_from_vdir(memory_region *mreg,
 				      const char *filepath_vdir)
 {
@@ -675,26 +653,13 @@ update_or_create_fuse_entry_from_vdir(memory_region *mreg,
 	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);
+	if (!updated_entry) {
+		return -EIO;
 	}
 
-	icalcomponent *entry_ics =
-	    get_icalcomponent_from_node(mreg, new_or_updated_node);
+	struct tree_node *node = upsert_fuse_node(updated_entry);
+
+	icalcomponent *entry_ics = get_icalcomponent_from_node(mreg, node);
 
 	const char *new_parent_uid = get_parent_uid(entry_ics);
 
@@ -702,21 +667,20 @@ update_or_create_fuse_entry_from_vdir(memory_region *mreg,
 		LOG("Has parent");
 		struct tree_node *new_parent =
 		    get_node_by_uuid(mreg, new_parent_uid);
-		move_node(new_parent, new_or_updated_node);
+		move_node(new_parent, 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);
+		move_node(fuse_root, node);
 	}
 
 	LOG("Tree updated");
+	return 0;
 }
 
 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;
@@ -738,7 +702,7 @@ delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
 	}
 	LOG("Deleting file %s, located at %s", filepath, filepath_original);
 
-	res = remove(filepath_original);
+	int res = remove(filepath_original);
 	if (res != 0) {
 		LOG("Failed to delete file");
 		return -res;
@@ -746,7 +710,7 @@ delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
 
 	delete_fuse_node(node);
 
-	return res;
+	return 0;
 }
 
 int
@@ -766,16 +730,6 @@ do_agenda_rename(memory_region *mreg, const char *old, const char *new)
 		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 =
@@ -797,8 +751,6 @@ do_agenda_rename(memory_region *mreg, const char *old, const char *new)
 								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,
@@ -806,7 +758,7 @@ do_agenda_rename(memory_region *mreg, const char *old, const char *new)
 					      child_ics);
 	}
 	else {
-		add_child(fuse_root, child_node);
+		move_node(fuse_root, child_node);
 	}
 	return 0;
 }
@@ -833,9 +785,7 @@ delete_from_fuse_path(memory_region *mreg, const char *filepath)
 		return -EIO;
 	}
 
-	hashmap_remove(entries_vdir, filepath_original);
-	detach_tree_node(node);
-	free_tree(node);
+	delete_fuse_node(node);
 	return res;
 }
 
@@ -886,22 +836,16 @@ get_fuse_path_from_vdir(memory_region *mreg, const char *filepath)
 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);
+	if (!node) {
+		return -ENOENT;
+	}
 
 	// 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);
+	delete_fuse_node(node);
 	return 0;
 }
diff --git a/fuse_node.h b/fuse_node.h
@@ -95,14 +95,9 @@ 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);
+struct stat
+get_node_stat(memory_region *mreg, const struct tree_node *node,
+	      icalcomponent *node_component);
 
 int
 insert_fuse_node_to_path(memory_region *mreg, const char *fuse_path,
diff --git a/fuse_node_store.c b/fuse_node_store.c
@@ -38,7 +38,6 @@ get_fuse_node_from_vdir_name(const char *vdir_name)
 const struct agenda_entry *
 get_entry(const struct tree_node *node)
 {
-	assert(node->data);
 	return node->data;
 }
 
@@ -72,3 +71,18 @@ create_fuse_node(const struct agenda_entry *entry)
 	return new_node;
 }
 
+struct tree_node *
+upsert_fuse_node(const struct agenda_entry *entry)
+{
+	struct tree_node *node =
+	    hashmap_get(entries_vdir, entry->filename_vdir);
+	if (node) {
+		set_node_filename(node, entry->filename);
+	}
+	else {
+		node = create_fuse_node(entry);
+	}
+
+	return node;
+}
+
diff --git a/fuse_node_store.h b/fuse_node_store.h
@@ -38,4 +38,7 @@ create_fuse_node(const struct agenda_entry *entry);
 void
 delete_fuse_node(struct tree_node *node);
 
+struct tree_node *
+upsert_fuse_node(const struct agenda_entry *entry);
+
 #endif // fuse_node_store_h_INCLUDED
diff --git a/main.c b/main.c
@@ -60,37 +60,10 @@ fuse_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 	}
 
 	icalcomponent *ic = get_icalcomponent_from_node(mreg, node);
-	stbuf->st_uid = get_node_uid(node);
-	stbuf->st_gid = get_node_gid(node);
-	stbuf->st_ino = get_node_ino(node);
 
-	int size = get_entry_content_size(ic);
-	stbuf->st_size = (off_t)size;
+	struct stat st = get_node_stat(mreg, node, ic);
 
-	if (node_is_directory(mreg, node, ic)) {
-		stbuf->st_mode = S_IFDIR | 0444;
-		stbuf->st_nlink = 2;
-	}
-	else {
-		stbuf->st_mode = S_IFREG | 0774;
-		stbuf->st_nlink = 1;
-	}
-
-	char *vdir_path = get_vdir_filepath(mreg, node);
-	struct stat vdir_file_stat;
-	if (stat(vdir_path, &vdir_file_stat) == 0) {
-		stbuf->st_mtime = vdir_file_stat.st_mtime;
-		stbuf->st_ctime = vdir_file_stat.st_ctime;
-	}
-	else {
-		icaltimetype ical_last_modified = get_last_modified(ic);
-
-		time_t last_modified = icaltime_as_timet(ical_last_modified);
-		LOG("STAT TIME IS: %ld", last_modified);
-
-		stbuf->st_mtime = last_modified;
-		stbuf->st_ctime = last_modified;
-	}
+	*stbuf = st;
 
 	stbuf->st_atime = time(NULL);
 
@@ -132,17 +105,7 @@ fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
 		icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
 		assert(ic);
 
-		struct stat st = {0};
-		// TODO: Correct perms
-		st.st_mode = S_IFREG | 0744;
-
-		st.st_uid = get_node_uid(child);
-		st.st_gid = get_node_gid(child);
-		st.st_ino = get_node_ino(child);
-
-		if (node_is_directory(mreg, child, ic)) {
-			st.st_mode = S_IFDIR | 0755;
-		}
+		struct stat st = get_node_stat(mreg, node, ic);
 
 		if (filler(buf, get_node_filename(mreg, child), &st, 0, 0) !=
 		    0) {
diff --git a/path.c b/path.c
@@ -21,6 +21,15 @@ without_file_extension(memory_region *m, const path *p)
 }
 
 path *
+get_file_extension(memory_region *m, const path *p)
+{
+	char *cpy = rstrdup(m, p);
+	char *last_dot = strrchr(cpy, '.');
+
+	return last_dot;
+}
+
+path *
 append_path(memory_region *m, const path *parentp, const char *childp)
 {
 	char *filepath = NULL;
diff --git a/path.h b/path.h
@@ -19,6 +19,9 @@ append_path(memory_region *m, const path *parentp, const char *childp);
 const path *
 get_filename(const char *full_path);
 
+path *
+get_file_extension(memory_region *m, const path *p);
+
 char *
 get_parent_path(memory_region *mreg, const char *path);