agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit 10d6f44f6aa113853131c0fbb4d80d5b8c598b72
parent b007ae8b1bc47cf56f4b0395eb1b4cb541a50fdd
Author: Marc Coquand <marc@coquand.email>
Date:   Tue, 22 Jul 2025 22:21:24 +0200

Handle file caching correctly

This makes sure the file previews etc. are updated correctly.

Unfortunately we use the mtime of the original ICS files, whereas
probably it would've been nicer to report the modified at within
the ICS file, but that creates issues because the stat happens
before the file is properly updated.

Fixes: https://todo.sr.ht/~marcc/agendafs/4

Diffstat:
Magenda_entry.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Magenda_entry.h | 6+++++-
Mmain.c | 97+++++++++++++++++++++++++++++++++++++++++++------------------------------------
3 files changed, 113 insertions(+), 47 deletions(-)
diff --git a/agenda_entry.c b/agenda_entry.c
@@ -44,6 +44,7 @@ copy_agenda_entry(const struct agenda_entry *src)
 	copy->filename_original = xstrdup(src->filename_original);
 	copy->gid = src->gid;
 	copy->uid = src->uid;
+	copy->ino = src->ino;
 	return copy;
 }
 
@@ -434,6 +435,7 @@ load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
 	// 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_original,
 	    new_entry->filename);
@@ -819,6 +821,14 @@ get_node_gid(const struct tree_node *node)
 	}
 	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;
+}
 
 icaltimetype
 get_last_modified(icalcomponent *component)
@@ -1183,12 +1193,55 @@ delete_from_fuse_path(memory_region *mreg, const char *filepath)
 	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_original(memory_region *mreg, const char *filepath)
+{
+	struct tree_node *node =
+	    hashmap_get(entries_original_ics, get_filename(filepath));
+
+	if (node) {
+		return get_node_path(mreg, node);
+	}
+	return NULL;
+}
+
 int
 delete_from_original_path(memory_region *mreg, const char *filepath)
 {
 	// Strip path, validate just the new basename
-	const char *keyname = strrchr(filepath, '/');
-	keyname++;
+	const char *keyname = get_filename(filepath);
 	struct agenda_entry *entry = hashmap_get(entries_original_ics, keyname);
 	if (!entry) {
 		return -EIO;
diff --git a/agenda_entry.h b/agenda_entry.h
@@ -30,6 +30,7 @@ struct agenda_entry {
 	char *filename_original;
 	uid_t uid;
 	gid_t gid;
+	ino_t ino;
 };
 
 extern char ICS_DIR[256];
@@ -160,7 +161,10 @@ set_node_class(memory_region *mreg, const struct tree_node *node,
 
 char *
 create_new_unique_ics_uid(memory_region *mreg);
-
+ino_t
+get_node_ino(const struct tree_node *node);
+char *
+get_fuse_path_from_original(memory_region *mreg, const char *filepath);
 icalcomponent *
 create_vjournal_entry(memory_region *mreg, const char *summary);
 
diff --git a/main.c b/main.c
@@ -1,3 +1,4 @@
+#include "fuse3/fuse_opt.h"
 #define FUSE_USE_VERSION 31
 
 #include "agenda_entry.h"
@@ -8,6 +9,7 @@
 #include <dirent.h>
 #include <errno.h>
 #include <fuse3/fuse.h>
+#include <fuse3/fuse_lowlevel.h>
 #include <libical/ical.h>
 #include <pthread.h>
 #include <regex.h>
@@ -25,7 +27,8 @@
 
 // Listening to file changes of the original content
 #define EVENT_BUF_LEN (1024 * (sizeof(struct inotify_event) + 16))
-
+static struct fuse *fuse_instance = NULL;
+static struct fuse_session *session = NULL;
 static pthread_mutex_t entries_mutex = PTHREAD_MUTEX_INITIALIZER;
 
 static int
@@ -48,6 +51,7 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 		// TODO: Probably should be set with an option?
 		stbuf->st_uid = getuid();
 		stbuf->st_gid = getgid();
+		stbuf->st_ino = 0;
 		goto cleanup_return;
 	}
 
@@ -61,31 +65,38 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 
 	LOG("icalcomponent from node");
 	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;
+
 	if (node_is_directory(mreg, node, ic)) {
 		stbuf->st_mode = S_IFDIR | 0444;
-		stbuf->st_uid = get_node_uid(node);
-		stbuf->st_gid = get_node_gid(node);
-
 		stbuf->st_nlink = 2;
 	}
 	else {
 		stbuf->st_mode = S_IFREG | 0774;
-		stbuf->st_uid = get_node_uid(node);
-		stbuf->st_gid = get_node_gid(node);
-
 		stbuf->st_nlink = 1;
 	}
 
-	int size = get_entry_content_size(ic);
-
-	stbuf->st_size = (off_t)size;
+	char *full_disk_path = get_original_filepath(mreg, node);
+	struct stat disk_stat;
+	if (stat(full_disk_path, &disk_stat) == 0) {
+		stbuf->st_mtime = disk_stat.st_mtime;
+		stbuf->st_ctime = disk_stat.st_ctime;
+	}
+	else {
+		icaltimetype ical_last_modified = get_last_modified(ic);
 
-	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);
 
-	time_t last_modified = icaltime_as_timet(ical_last_modified);
+		stbuf->st_mtime = last_modified;
+		stbuf->st_ctime = last_modified;
+	}
 
-	stbuf->st_mtime = last_modified;
-	stbuf->st_ctime = last_modified;
 	stbuf->st_atime = time(NULL);
 
 	ret_code = 0;
@@ -118,30 +129,28 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
 	filler(buf, "..", NULL, 0, 0);
 
 	const struct tree_node *node = get_node_by_path(mreg, path);
-	if (node) {
-		for (size_t i = 0; i < node->child_count; i++) {
-			const struct tree_node *child = node->children[i];
-			icalcomponent *ic =
-			    get_icalcomponent_from_node(mreg, child);
-
-			struct stat st = {0};
-			// TODO: Fix
-			st.st_ino = i + 1;
-			st.st_mode = S_IFREG | 0744;
+	if (!node) {
+		status = -ENOENT;
+		goto cleanup_return;
+	}
+	for (size_t i = 0; i < node->child_count; i++) {
+		const struct tree_node *child = node->children[i];
+		icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
+		assert(ic);
 
-			st.st_uid = get_node_uid(child);
-			st.st_gid = get_node_gid(child);
+		struct stat st = {0};
+		// TODO: Fix
+		st.st_mode = S_IFREG | 0744;
 
-			if (node_is_directory(mreg, child, ic)) {
-				st.st_mode = S_IFDIR | 0755;
-			}
+		st.st_uid = get_node_uid(child);
+		st.st_gid = get_node_gid(child);
+		st.st_ino = get_node_ino(child);
 
-			filler(buf, get_node_filename(mreg, child), &st, 0, 0);
+		if (node_is_directory(mreg, child, ic)) {
+			st.st_mode = S_IFDIR | 0755;
 		}
-	}
-	else {
-		status = -ENOENT;
-		goto cleanup_return;
+
+		filler(buf, get_node_filename(mreg, child), &st, 0, 0);
 	}
 
 cleanup_return:
@@ -214,7 +223,6 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
 	}
 
 	if (size > 0) {
-
 		LOG("Memcopying");
 		memcpy(buf, content + offset, size);
 	}
@@ -235,12 +243,12 @@ journal_write(const char *path, const char *buf, size_t size, off_t offset,
 
 	LOG("WRITE %s, %zu, %zu", buf, size, offset);
 	memory_region *mreg = create_region();
+	int ret_i = 0;
 	pthread_mutex_lock(&entries_mutex);
 	const struct tree_node *node = get_node_by_path(mreg, path);
 	if (!node) {
-		pthread_mutex_unlock(&entries_mutex);
-		rfree_all(mreg);
-		return -ENOENT;
+		ret_i = -ENONET;
+		goto cleanup_return;
 	}
 
 	icalcomponent *ic = get_icalcomponent_from_node(mreg, node);
@@ -249,18 +257,19 @@ journal_write(const char *path, const char *buf, size_t size, off_t offset,
 		old_desc = "";
 
 	char *new_desc = rstrins(mreg, old_desc, offset, buf, size);
-
 	icalcomponent_set_description(ic, new_desc);
 
 	if (write_ical_file(mreg, node, ic) != 0) {
-		pthread_mutex_unlock(&entries_mutex);
-		rfree_all(mreg);
-		return -EIO;
+		ret_i = -EIO;
+		goto cleanup_return;
 	}
+
+	ret_i = size;
+
+cleanup_return:
 	pthread_mutex_unlock(&entries_mutex);
 	rfree_all(mreg);
-
-	return size;
+	return ret_i;
 }
 
 // Aka remove or delete