agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 16bca4d3c54de3d0b348cd99728142af286915b4
parent 3a171b4ea68f1d091d61a1eec55c2123e9a55f40
Author: Marc Coquand <marc@coquand.email>
Date: Fri, 22 Aug 2025 18:29:48 +0200
Add draft/final support
Implements: https://todo.sr.ht/~marcc/agendafs/39
Diffstat:
6 files changed, 131 insertions(+), 3 deletions(-)
diff --git a/agendafs.8.scd b/agendafs.8.scd
@@ -51,6 +51,10 @@ usage. Built-in extended attributes are:
Date-time start, ISO-formatted. Specifies the "beginning" of the
file.
+*user.status*
+ Either *draft* or *final*. Can not be removed. By default,
+ directories are set to *final* while files are set to *draft*.
+
Arbitrary user-provided attributes are also supported. Limits are set to
255 bytes for attributes and 64 kilobytes for their value.
diff --git a/fuse_node.c b/fuse_node.c
@@ -106,7 +106,6 @@ write_ical_file(memory_region *mreg, const struct tree_node *node,
icalproperty_set_lastmodified(prop, get_ical_now());
}
- icalcomponent_set_status(ic, ICAL_STATUS_FINAL);
icalcomponent *inner = icalcomponent_get_inner(ic);
const char *descr_prop = icalcomponent_get_description(inner);
@@ -518,6 +517,31 @@ get_node_class(memory_region *mreg, const struct tree_node *node)
return format_ical_class(classv);
}
+const char *
+get_node_status(memory_region *mreg, const struct tree_node *node)
+{
+
+ // TODO: Take this as parameter instead...
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return 0;
+ }
+
+ icalcomponent *inner = icalcomponent_get_inner(component);
+
+ const icalproperty *statusp =
+ icalcomponent_get_first_property(inner, ICAL_STATUS_PROPERTY);
+
+ if (!statusp) {
+ LOG("No status found");
+ return NULL;
+ }
+
+ const enum icalproperty_status statusv =
+ icalproperty_get_status(statusp);
+ return format_ical_status(statusv);
+}
+
int
delete_node_class(memory_region *mreg, const struct tree_node *node)
{
@@ -556,6 +580,20 @@ set_node_class(memory_region *mreg, const struct tree_node *node,
return write_ical_file(mreg, node, component);
}
+int
+set_node_status(memory_region *mreg, const struct tree_node *node,
+ const icalproperty_status new_status)
+{
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return 0;
+ }
+
+ icalcomponent_set_status(component, new_status);
+
+ return write_ical_file(mreg, node, component);
+}
+
size_t
calculate_node_size(memory_region *mreg, const struct tree_node *node,
icalcomponent *node_component)
diff --git a/fuse_node.h b/fuse_node.h
@@ -79,6 +79,9 @@ set_node_categories(memory_region *mreg, const struct tree_node *node,
const char *
get_node_class(memory_region *mreg, const struct tree_node *node);
+const char *
+get_node_status(memory_region *mreg, const struct tree_node *node);
+
int
delete_node_class(memory_region *mreg, const struct tree_node *node);
@@ -86,6 +89,10 @@ int
set_node_class(memory_region *mreg, const struct tree_node *node,
const icalproperty_class new_class);
+int
+set_node_status(memory_region *mreg, const struct tree_node *node,
+ const icalproperty_status new_status);
+
struct stat
get_node_stat(memory_region *mreg, const struct tree_node *node,
icalcomponent *node_component);
diff --git a/ical_extra.c b/ical_extra.c
@@ -65,6 +65,19 @@ parse_ical_class(const char *input)
return ICAL_CLASS_NONE;
}
+const icalproperty_status
+parse_ical_status(const char *input)
+{
+ // FIXME should check if VJOURNAL, VEVENT or VTODO
+ if (strcmp(input, "draft") == 0) {
+ return ICAL_STATUS_DRAFT;
+ }
+ else if (strcmp(input, "final") == 0) {
+ return ICAL_STATUS_FINAL;
+ }
+ return ICAL_STATUS_FINAL;
+}
+
const char *
format_ical_class(const enum icalproperty_class iclass)
{
@@ -80,6 +93,18 @@ format_ical_class(const enum icalproperty_class iclass)
return NULL;
}
+const char *
+format_ical_status(const enum icalproperty_status istatus)
+{
+ if (istatus == ICAL_STATUS_DRAFT) {
+ return "draft";
+ }
+ else if (istatus == ICAL_STATUS_FINAL) {
+ return "final";
+ }
+ return NULL;
+}
+
bool
is_directory_component(icalcomponent *component)
{
@@ -181,6 +206,7 @@ create_vjournal_directory(memory_region *mreg, const char *summary)
icalproperty *private = icalproperty_new_class(ICAL_CLASS_PRIVATE);
icalcomponent_add_property(journal, private);
+ icalcomponent_set_status(journal, ICAL_STATUS_FINAL);
icalproperty *is_directory = icalproperty_new_x(IS_DIRECTORY_PROPERTY);
icalproperty_set_x_name(is_directory, IS_DIRECTORY_PROPERTY);
@@ -219,6 +245,8 @@ create_vjournal_entry(memory_region *mreg, const char *summary)
icalproperty *private = icalproperty_new_class(ICAL_CLASS_PRIVATE);
icalcomponent_add_property(journal, private);
+ icalcomponent_set_status(journal, ICAL_STATUS_DRAFT);
+
char *without_extension = without_file_extension(mreg, summary);
icalcomponent_add_property(journal,
diff --git a/ical_extra.h b/ical_extra.h
@@ -45,9 +45,13 @@ set_parent_child_relationship_to_component(icalcomponent *parent,
const icalproperty_class
parse_ical_class(const char *input);
+const icalproperty_status
+parse_ical_status(const char *input);
+
const char *
format_ical_class(const enum icalproperty_class iclass);
-
+const char *
+format_ical_status(const enum icalproperty_status istatus);
void
icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
const char *buf, size_t size, off_t offset);
diff --git a/main.c b/main.c
@@ -383,6 +383,12 @@ fuse_removexattr(const char *path, const char *attribute)
status = delete_node_class(mreg, node);
goto cleanup_return;
}
+
+ // It should probably always be set
+ else if (strcmp(attribute, "user.status") == 0) {
+ status = -EPERM;
+ goto cleanup_return;
+ }
if (strcmp(attribute, "user.dtstart") == 0) {
status = clear_dtstart(mreg, node);
goto cleanup_return;
@@ -454,11 +460,13 @@ fuse_setxattr(const char *path, const char *attribute, const char *value,
}
else if (strcmp(attribute, "user.categories") == 0) {
+ LOG("Updating categories");
status = set_node_categories(mreg, node, value, s);
goto cleanup_return;
}
else if (strcmp(attribute, "user.class") == 0) {
+ LOG("Updating class");
enum icalproperty_class iclass = parse_ical_class(value);
if (iclass == ICAL_CLASS_NONE) {
@@ -469,10 +477,25 @@ fuse_setxattr(const char *path, const char *attribute, const char *value,
status = set_node_class(mreg, node, iclass);
goto cleanup_return;
}
+ else if (strcmp(attribute, "user.status") == 0) {
+ LOG("Updating status");
+ enum icalproperty_status istatus = parse_ical_status(value);
+
+ if (istatus == ICAL_STATUS_NONE) {
+ LOG("INVALID STATUS");
+ status = -EINVAL;
+ goto cleanup_return;
+ }
+
+ status = set_node_status(mreg, node, istatus);
+ goto cleanup_return;
+ }
else if (starts_with_str(attribute, "user.")) {
icalcomponent *ic = get_icalcomponent_from_node(mreg, node);
const char *rkey = attribute + 5;
+ LOG("Updating %s", rkey);
+
icalcomponent_set_custom_x_value(mreg, ic, rkey, value);
status = write_ical_file(mreg, node, ic);
goto cleanup_return;
@@ -533,12 +556,20 @@ fuse_listxattr(const char *path, char *list, size_t size)
fprintf(stream, "user.uid");
fputc('\0', stream);
+ LOG("Checking node class");
const char *iclass = get_node_class(mreg, node);
if (iclass != NULL && strcmp("", iclass) != 0) {
fprintf(stream, "user.class");
fputc('\0', stream);
}
+ LOG("Checking node status");
+ const char *istatus = get_node_status(mreg, node);
+ if (istatus != NULL && strcmp("", istatus) != 0) {
+ fprintf(stream, "user.status");
+ fputc('\0', stream);
+ }
+
icalcomponent_print_x_props(stream, comp);
if (fclose(stream) != 0) {
@@ -547,7 +578,7 @@ fuse_listxattr(const char *path, char *list, size_t size)
}
if (size > 0) {
- if (xattr_list_len < size) {
+ if (xattr_list_len > size) {
memcpy(list, xattribute_list, xattr_list_len);
}
else {
@@ -626,6 +657,22 @@ fuse_getxattr(const char *path, const char *attribute, char *buf, size_t s)
status = string_len;
goto cleanup_return;
}
+ else if (strcmp(attribute, "user.status") == 0) {
+ const char *c = get_node_status(mreg, node);
+ if (!c || *c == '\0') {
+ status = -ENODATA;
+ goto cleanup_return;
+ }
+
+ int string_len = strlen(c);
+
+ if (s > 0) {
+ snprintf(buf, s, "%s", c);
+ }
+
+ status = string_len;
+ goto cleanup_return;
+ }
else if (strcmp(attribute, "user.dtstart") == 0) {
const char *res = get_dtstart(mreg, node);
if (!res) {