agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit f207f67b28ac7467ba101f8ae0a65d3c3d2d615c
parent 6ae0f55754e057cf2db7ba3c6538001d7c315440
Author: Marc Coquand <marc@coquand.email>
Date:   Thu, 10 Jul 2025 22:41:12 +0200

Add class support

Implements: https://todo.sr.ht/~marcc/agendafs/16

Diffstat:
Mjournal_entry.c | 91++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mjournal_entry.h | 13+++++++++++++
Mmain.c | 171++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
3 files changed, 229 insertions(+), 46 deletions(-)
diff --git a/journal_entry.c b/journal_entry.c
@@ -742,7 +742,7 @@ append_category_to_memstream(FILE *memstream, const char *category,
 }
 
 // Returns comma separated list of categories for a file
-const char *
+char *
 get_node_categories(const struct tree_node *node)
 {
 	const struct journal_entry *entry = node->data;
@@ -845,6 +845,95 @@ set_node_categories(const struct tree_node *node, const char *new_categories,
 	return write_entry_to_ical_file(entry);
 }
 
+const char *
+format_ical_class(const enum icalproperty_class iclass)
+{
+	if (iclass == ICAL_CLASS_PRIVATE) {
+		return "private";
+	}
+	else if (iclass == ICAL_CLASS_PUBLIC) {
+		return "public";
+	}
+	else if (iclass == ICAL_CLASS_CONFIDENTIAL) {
+		return "confidential";
+	}
+	return NULL;
+}
+
+const icalproperty_class
+parse_ical_class(const char *input)
+{
+	if (strcmp(input, "private") == 0) {
+		return ICAL_CLASS_PRIVATE;
+	}
+	else if (strcmp(input, "public") == 0) {
+		return ICAL_CLASS_PUBLIC;
+	}
+	else if (strcmp(input, "confidential") == 0) {
+		return ICAL_CLASS_CONFIDENTIAL;
+	}
+	return ICAL_CLASS_X;
+}
+
+const char *
+get_node_class(const struct tree_node *node)
+{
+	const struct journal_entry *entry = node->data;
+	if (!entry) {
+		return "";
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(entry->component);
+
+	const icalproperty *classp =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	const enum icalproperty_class classv = icalproperty_get_class(classp);
+
+	if (classp) {
+		return format_ical_class(classv);
+	}
+
+	return "";
+}
+
+int
+delete_node_class(const struct tree_node *node)
+{
+	const struct journal_entry *entry = node->data;
+	if (!entry) {
+		return -ENOENT;
+	}
+	icalcomponent *inner = icalcomponent_get_inner(entry->component);
+	icalproperty *prop =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	icalcomponent_remove_property(inner, prop);
+
+	return write_entry_to_ical_file(entry);
+}
+
+int
+set_node_class(const struct tree_node *node, const icalproperty_class new_class)
+{
+	const struct journal_entry *entry = node->data;
+	if (!entry) {
+		return -ENOENT;
+	}
+
+	icalcomponent *inner = icalcomponent_get_inner(entry->component);
+
+	icalproperty *classp =
+	    icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+	icalcomponent_remove_property(inner, classp);
+
+	icalproperty *new_class_prop = icalproperty_new_class(new_class);
+	icalcomponent_add_property(inner, new_class_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
@@ -136,7 +136,20 @@ build_today_hashmap();
 struct tree_node *
 get_node_by_path(const char *path);
 
+const icalproperty_class
+parse_ical_class(const char *);
+
 const char *
+get_node_class(const struct tree_node *node);
+
+int
+delete_node_class(const struct tree_node *node);
+
+int
+set_node_class(const struct tree_node *node,
+	       const icalproperty_class new_class);
+
+char *
 get_node_categories(const struct tree_node *);
 
 int
diff --git a/main.c b/main.c
@@ -331,21 +331,34 @@ journal_removexattr(const char *path, const char *header)
 {
 	LOG("REMOVEXATTR '%s' '%s'", path, header);
 
-	if (strcmp(header, "user.categories") != 0) {
-		return -EINVAL;
-	}
+	if (strcmp(header, "user.categories") == 0) {
+		pthread_mutex_lock(&entries_mutex);
+		struct tree_node *node = get_node_by_path(path);
+		if (!node) {
+			pthread_mutex_unlock(&entries_mutex);
+			return -ENONET;
+		}
 
-	pthread_mutex_lock(&entries_mutex);
-	struct tree_node *node = get_node_by_path(path);
-	if (!node) {
+		int status = set_node_categories(node, "", 0);
 		pthread_mutex_unlock(&entries_mutex);
-		return -ENONET;
+
+		return status;
 	}
+	else if (strcmp(header, "user.class") == 0) {
+		pthread_mutex_lock(&entries_mutex);
+		struct tree_node *node = get_node_by_path(path);
+		if (!node) {
+			pthread_mutex_unlock(&entries_mutex);
+			return -ENONET;
+		}
 
-	int status = set_node_categories(node, "", 0);
-	pthread_mutex_unlock(&entries_mutex);
+		int status = delete_node_class(node);
+		pthread_mutex_unlock(&entries_mutex);
+
+		return status;
+	}
 
-	return status;
+	return -EINVAL;
 }
 
 static int
@@ -355,50 +368,98 @@ journal_setxattr(const char *path, const char *header,
 	LOG("SETXATTR '%s' '%s' '%zu' '%s' '%d'", path, header, s,
 	    new_attributes, flags);
 
-	if (strcmp(header, "user.categories") != 0) {
-		return -EINVAL;
-	}
+	if (strcmp(header, "user.categories") == 0) {
+		pthread_mutex_lock(&entries_mutex);
+		struct tree_node *node = get_node_by_path(path);
+		if (!node) {
+			pthread_mutex_unlock(&entries_mutex);
 
-	pthread_mutex_lock(&entries_mutex);
-	struct tree_node *node = get_node_by_path(path);
-	if (!node) {
+			return -ENONET;
+		}
+
+		int status = set_node_categories(node, new_attributes, s);
 		pthread_mutex_unlock(&entries_mutex);
 
-		return -ENONET;
+		return status;
 	}
+	else if (strcmp(header, "user.class") == 0) {
+		pthread_mutex_lock(&entries_mutex);
+		struct tree_node *node = get_node_by_path(path);
+		if (!node) {
+			pthread_mutex_unlock(&entries_mutex);
+			return -ENONET;
+		}
 
-	int status = set_node_categories(node, new_attributes, s);
-	pthread_mutex_unlock(&entries_mutex);
+		enum icalproperty_class iclass =
+		    parse_ical_class(new_attributes);
 
-	return status;
+		if (iclass == ICAL_CLASS_X) {
+			pthread_mutex_unlock(&entries_mutex);
+			return -EINVAL;
+		}
+
+		int status = set_node_class(node, iclass);
+		pthread_mutex_unlock(&entries_mutex);
+
+		return status;
+	}
+
+	return -EINVAL;
 }
 
 static int
 journal_listxattr(const char *path, char *list, size_t size)
 {
+	LOG("LISTXATTR %s %zu", list, size);
+	FILE *stream;
+	char *membuffer = NULL;
+	size_t membuffer_len = 0;
 
 	pthread_mutex_lock(&entries_mutex);
 	struct tree_node *node = get_node_by_path(path);
 	if (!node) {
 		pthread_mutex_unlock(&entries_mutex);
-
 		return -ENONET;
 	}
 
-	const char *cats = get_node_categories(node);
-	pthread_mutex_unlock(&entries_mutex);
+	stream = open_memstream(&membuffer, &membuffer_len);
+	if (stream == NULL) {
+		pthread_mutex_unlock(&entries_mutex);
+		return -EIO;
+	}
+	char *cats = get_node_categories(node);
+	if (cats != NULL && strcmp("", cats) != 0) {
+		fprintf(stream, "user.categories");
+		fputc('\0', stream);
+	}
+	free(cats);
 
-	if (strcmp("", cats) == 0) {
-		return 0;
+	const char *iclass = get_node_class(node);
+	if (iclass != NULL && strcmp("", iclass) != 0) {
+		fprintf(stream, "user.class");
+		fputc('\0', stream);
+	}
+
+	if (fclose(stream) != 0) {
+		free(membuffer);
+
+		pthread_mutex_unlock(&entries_mutex);
+		return -EIO;
 	}
 
-	const char *attr_name = "user.categories";
-	size_t attr_name_len = strlen(attr_name);
 	if (size > 0) {
-		strncpy(list, attr_name, size - 1);
-		list[size - 1] = '\0';
+		if (membuffer_len < size) {
+			memcpy(list, membuffer, membuffer_len);
+		}
+		else {
+			memcpy(list, membuffer, size);
+			list[size - 1] = '\0';
+		}
 	}
-	return attr_name_len;
+
+	free(membuffer);
+	pthread_mutex_unlock(&entries_mutex);
+	return membuffer_len;
 }
 
 static int
@@ -406,29 +467,49 @@ 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;
-	}
+	if (strcmp(header, "user.categories") == 0) {
+		pthread_mutex_lock(&entries_mutex);
+		struct tree_node *node = get_node_by_path(path);
+		if (!node) {
+			pthread_mutex_unlock(&entries_mutex);
 
-	pthread_mutex_lock(&entries_mutex);
-	struct tree_node *node = get_node_by_path(path);
-	if (!node) {
+			return -ENONET;
+		}
+
+		const char *cats = get_node_categories(node);
 		pthread_mutex_unlock(&entries_mutex);
 
-		return -ENONET;
+		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;
 	}
+	else if (strcmp(header, "user.class") == 0) {
+		pthread_mutex_lock(&entries_mutex);
+		struct tree_node *node = get_node_by_path(path);
+		if (!node) {
+			pthread_mutex_unlock(&entries_mutex);
 
-	const char *cats = get_node_categories(node);
-	pthread_mutex_unlock(&entries_mutex);
+			return -ENONET;
+		}
+
+		const char *c = get_node_class(node);
+		pthread_mutex_unlock(&entries_mutex);
 
-	int string_len = strlen(cats);
+		int string_len = strlen(c);
 
-	// If s == 0 we don't need to write, just return size to allocate
-	if (s > 0) {
-		snprintf(buf, s, "%s", cats);
-	}
+		if (s > 0) {
+			snprintf(buf, s, "%s", c);
+		}
 
-	return string_len;
+		return string_len;
+	}
+	return -ENODATA;
 }
 
 static void *