agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit 94f608109c357719f8016b33c4177b32f32731a3
parent ebde89072fc4eede36efd1ed68c850890b4de89d
Author: Marc Coquand <marc@coquand.email>
Date:   Thu, 10 Jul 2025 10:46:06 +0200

Add setxattr for user categories

References: https://todo.sr.ht/~marcc/agendafs/1

Diffstat:
Mjournal_entry.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjournal_entry.h | 6++++++
Mmain.c | 68+++++++++++++++++++++++++++++++++++++++++++++-----------------------
3 files changed, 112 insertions(+), 23 deletions(-)
diff --git a/journal_entry.c b/journal_entry.c
@@ -784,6 +784,67 @@ get_node_categories(const struct tree_node *node)
 	return result_buffer;
 }
 
+int
+delete_node_categories(const struct tree_node *node)
+{
+	const struct journal_entry *entry = node->data;
+	if (!entry) {
+		return -ENOENT;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(entry->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_entry_to_ical_file(entry);
+}
+
+int
+set_node_categories(const struct tree_node *node, const char *new_categories,
+		    size_t s)
+{
+	const struct journal_entry *entry = node->data;
+	if (!entry) {
+		return -ENOENT;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(entry->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_entry_to_ical_file(entry);
+	}
+
+	char *null_terminated_categories = xmalloc(s + 1);
+	strncpy(null_terminated_categories, new_categories, s);
+	null_terminated_categories[s] = '\0';
+
+	icalproperty *new_cat_prop =
+	    icalproperty_new_categories(null_terminated_categories);
+	icalcomponent_add_property(inner, new_cat_prop);
+
+	return write_entry_to_ical_file(entry);
+}
+
 size_t
 write_entry_to_ical_file(const struct journal_entry *entry)
 {
diff --git a/journal_entry.h b/journal_entry.h
@@ -140,6 +140,12 @@ const char *
 get_node_categories(const struct tree_node *);
 
 int
+delete_node_categories(const struct tree_node *);
+
+int
+set_node_categories(const struct tree_node *, const char *, size_t s);
+
+int
 build_notes_hashmap();
 
 size_t
diff --git a/main.c b/main.c
@@ -326,6 +326,50 @@ journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
 	return 0;
 }
 
+static int
+journal_setxattr(const char *path, const char *header,
+		 const char *new_attributes, size_t s, int flags)
+{
+	LOG("SETXATTR '%s' '%s' '%zu' '%s' '%d'", path, header, s,
+	    new_attributes, flags);
+
+	if (strcmp(header, "user.categories") != 0) {
+		return -EINVAL;
+	}
+
+	pthread_mutex_lock(&entries_mutex);
+	struct tree_node *node = get_node_by_path(path);
+	int status = set_node_categories(node, new_attributes, s);
+	pthread_mutex_unlock(&entries_mutex);
+
+	return status;
+}
+
+static int
+journal_getxattr(const char *path, const char *header, char *buf, size_t s)
+{
+	LOG("GETXATTR '%s' '%s' '%zu'\n", path, header, s);
+
+	if (strcmp(header, "user.categories") != 0) {
+		return -ENODATA;
+	}
+
+	pthread_mutex_lock(&entries_mutex);
+	struct tree_node *node = get_node_by_path(path);
+
+	const char *cats = get_node_categories(node);
+	pthread_mutex_unlock(&entries_mutex);
+
+	int string_len = strlen(cats);
+
+	// If s == 0 we don't need to write, just return size to allocate
+	if (s > 0) {
+		snprintf(buf, s, "%s", cats);
+	}
+
+	return string_len;
+}
+
 static void *
 watch_ics_dir(void *arg)
 {
@@ -414,29 +458,6 @@ journal_utimens(const char *path, const struct timespec tv[2],
 	return 0;
 }
 
-static int
-journal_getxattr(const char *path, const char *header, char *buf, size_t s)
-{
-	LOG("GETXATTR '%s' '%s' '%zu'\n", path, header, s);
-
-	if (strcmp(header, "user.categories") != 0) {
-		return -ENODATA;
-	}
-
-	struct tree_node *node = get_node_by_path(path);
-
-	const char *cats = get_node_categories(node);
-
-	int string_len = strlen(cats);
-
-	// If s == 0 we don't need to write, just return size to allocate
-	if (s > 0) {
-		snprintf(buf, s, "%s", cats);
-	}
-
-	return string_len;
-}
-
 struct fuse_operations journal_oper = {.getattr = journal_getattr,
 				       .readdir = journal_readdir,
 				       .open = journal_open,
@@ -447,6 +468,7 @@ struct fuse_operations journal_oper = {.getattr = journal_getattr,
 				       .mkdir = journal_mkdir,
 				       .unlink = journal_unlink,
 				       .getxattr = journal_getxattr,
+				       .setxattr = journal_setxattr,
 				       .rmdir = journal_rmdir,
 				       .readlink = journal_readlink,
 				       .rename = journal_rename};