agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 698eb179ffe9bd84766044e28267970695e9765c
parent 0e1a87120d934b61c81efe2a865cf1931875e167
Author: Marc Coquand <marc@coquand.email>
Date: Mon, 28 Jul 2025 12:40:25 +0200
Add file extension support
This solution makes it necessary to write to the ics files that
hey this is actually a directory. That requires actually having
write access which I hope is the case.
Implements: https://todo.sr.ht/~marcc/agendafs/5
Diffstat:
7 files changed, 141 insertions(+), 13 deletions(-)
diff --git a/fuse_node.c b/fuse_node.c
@@ -62,7 +62,7 @@ get_node_by_path(memory_region *mreg, const char *path)
for (size_t j = 0; j < current->child_count; ++j) {
const char *child_name =
- get_node_filename(mreg, current->children[j]);
+ get_node_filename(current->children[j]);
if (strcmp(child_name, segment) == 0) {
next = current->children[j];
@@ -158,8 +158,27 @@ int
update_summary(memory_region *mreg, icalcomponent *ics,
const struct tree_node *node)
{
- const struct agenda_entry *e = get_entry(node);
- icalcomponent_set_summary(ics, e->filename);
+ if (is_directory_component(ics)) {
+ icalcomponent_set_summary(ics, get_node_filename(node));
+ }
+ else {
+ char *filename_no_ext =
+ without_file_extension(mreg, get_node_filename(node));
+ icalcomponent_set_summary(ics, filename_no_ext);
+ }
+
+ return write_ical_file(mreg, node, ics);
+}
+
+int
+update_node_file_extension(memory_region *mreg, icalcomponent *ics,
+ const struct tree_node *node)
+{
+ const char *ext = get_file_extension(mreg, get_node_filename(node));
+ if (!ext) {
+ return 0;
+ }
+ icalcomponent_set_file_extension(ics, ext);
return write_ical_file(mreg, node, ics);
}
@@ -179,14 +198,28 @@ parse_ics_to_agenda_entry(memory_region *mreg, const char *vdir_filepath)
LOG("Not journal component");
return NULL;
}
- if (icalcomponent_get_summary(component) == NULL) {
+ const char *summary = icalcomponent_get_summary(component);
+ if (summary == NULL) {
LOG("No summary found");
return NULL;
}
+ char *filename = NULL;
+ if (is_directory_component(component)) {
+ filename = rstrdup(mreg, summary);
+ }
+ else {
+ const char *extension =
+ icalcomponent_get_file_extension(component);
+ if (!extension) {
+ extension = "txt";
+ }
+
+ rasprintf(mreg, &filename, "%s.%s", summary, extension);
+ }
+
struct agenda_entry *e =
- create_agenda_entry(mreg, icalcomponent_get_summary(component),
- get_filename(vdir_filepath));
+ create_agenda_entry(mreg, filename, get_filename(vdir_filepath));
return e;
}
@@ -269,6 +302,15 @@ load_root_node_tree()
struct tree_node *parent =
get_node_by_uuid(mreg, parent_uid);
if (parent) {
+
+ icalcomponent *pic =
+ get_icalcomponent_from_node(mreg, parent);
+ if (!is_directory_component(pic)) {
+ icalcomponent_mark_as_directory(pic);
+ assert(write_ical_file(mreg, parent,
+ pic) == 0);
+ }
+
move_fuse_node(parent, child);
}
else {
@@ -626,11 +668,18 @@ 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, node);
+ move_fuse_node(new_parent, node);
+
+ icalcomponent *pic =
+ get_icalcomponent_from_node(mreg, new_parent);
+ if (!is_directory_component(pic)) {
+ icalcomponent_mark_as_directory(pic);
+ assert(write_ical_file(mreg, new_parent, pic) == 0);
+ }
}
else {
LOG("Does not have parent, adding to root");
- move_node(fuse_root, node);
+ move_fuse_node(fuse_root, node);
}
LOG("Tree updated");
@@ -670,6 +719,7 @@ do_agenda_rename(memory_region *mreg, const char *old, const char *new)
set_node_filename(child_node, new_filename);
update_summary(mreg, child_ics, child_node);
+ update_node_file_extension(mreg, child_ics, child_node);
if (old_parent_node && old_parent_ics) {
remove_parent_child_relationship_from_component(old_parent_ics,
diff --git a/fuse_node_store.c b/fuse_node_store.c
@@ -56,12 +56,12 @@ is_root_node(const struct tree_node *node)
}
const char *
-get_node_filename(memory_region *mreg, const struct tree_node *node)
+get_node_filename(const struct tree_node *node)
{
struct agenda_entry *entry = node->data;
if (entry == NULL)
return "";
- return rstrdup(mreg, entry->filename);
+ return entry->filename;
}
const char *
diff --git a/fuse_node_store.h b/fuse_node_store.h
@@ -33,7 +33,7 @@ bool
is_root_node(const struct tree_node *node);
const char *
-get_node_filename(memory_region *mreg, const struct tree_node *node);
+get_node_filename(const struct tree_node *node);
int
set_node_filename(struct tree_node *node, const char *filename);
diff --git a/ical_extra.c b/ical_extra.c
@@ -4,6 +4,7 @@
#include "uuid/uuid.h"
#include <stdbool.h>
const char *IS_DIRECTORY_PROPERTY = "X-CALDAVFS-ISDIRECTORY";
+const char *FILE_EXTENSION_PROPERTY = "X-CALDAVFS-FILEEXT";
icaltimetype
get_last_modified(icalcomponent *component)
@@ -172,6 +173,16 @@ create_vjournal_directory(memory_region *mreg, const char *summary)
return calendar;
}
+void
+icalcomponent_mark_as_directory(icalcomponent *ic)
+{
+ icalcomponent *inner = icalcomponent_get_inner(ic);
+ icalproperty *is_directory = icalproperty_new_x(IS_DIRECTORY_PROPERTY);
+ icalproperty_set_x_name(is_directory, IS_DIRECTORY_PROPERTY);
+ icalproperty_set_x(is_directory, "YES");
+ icalcomponent_add_property(inner, is_directory);
+}
+
icalcomponent *
create_vjournal_entry(memory_region *mreg, const char *summary)
{
@@ -280,3 +291,58 @@ icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
icalcomponent_set_description(ic, new_desc);
}
+void
+icalcomponent_set_file_extension(icalcomponent *component,
+ const char *extension)
+{
+ icalcomponent *inner =
+ icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
+ assert(inner);
+
+ icalproperty *prop_iter =
+ icalcomponent_get_first_property(inner, ICAL_X_PROPERTY);
+ while (prop_iter != NULL) {
+ icalproperty *next_prop =
+ icalcomponent_get_next_property(inner, ICAL_X_PROPERTY);
+ const char *prop_name = icalproperty_get_x_name(prop_iter);
+ if (prop_name &&
+ strcmp(prop_name, FILE_EXTENSION_PROPERTY) == 0) {
+ icalcomponent_remove_property(inner, prop_iter);
+ icalproperty_free(prop_iter);
+ break;
+ }
+ prop_iter = next_prop;
+ }
+
+ icalproperty *fileext_prop =
+ icalproperty_new_x(FILE_EXTENSION_PROPERTY);
+
+ icalproperty_set_x_name(fileext_prop, FILE_EXTENSION_PROPERTY);
+ icalproperty_set_x(fileext_prop, extension);
+
+ icalcomponent_add_property(inner, fileext_prop);
+ LOG("Set X-CALDAVFS-FILEEXT to '%s' on component.", extension);
+}
+
+const char *
+icalcomponent_get_file_extension(icalcomponent *component)
+{
+ icalcomponent *inner =
+ icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
+ if (!inner) {
+ return NULL;
+ }
+
+ for (icalproperty *prop =
+ icalcomponent_get_first_property(inner, ICAL_X_PROPERTY);
+ prop != NULL;
+ prop = icalcomponent_get_next_property(inner, ICAL_X_PROPERTY)) {
+
+ const char *prop_name = icalproperty_get_x_name(prop);
+ if (prop_name &&
+ strcmp(prop_name, FILE_EXTENSION_PROPERTY) == 0) {
+ return icalproperty_get_value_as_string(prop);
+ }
+ }
+ return NULL;
+}
diff --git a/ical_extra.h b/ical_extra.h
@@ -51,4 +51,14 @@ format_ical_class(const enum icalproperty_class iclass);
void
icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
const char *buf, size_t size, off_t offset);
+
+void
+icalcomponent_set_file_extension(icalcomponent *component,
+ const char *extension);
+
+const char *
+icalcomponent_get_file_extension(icalcomponent *component);
+
+void
+icalcomponent_mark_as_directory(icalcomponent *ic);
#endif // ical_extra_h_INCLUDED
diff --git a/main.c b/main.c
@@ -107,8 +107,7 @@ fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
struct stat st = get_node_stat(mreg, node, ic);
- if (filler(buf, get_node_filename(mreg, child), &st, 0, 0) !=
- 0) {
+ if (filler(buf, get_node_filename(child), &st, 0, 0) != 0) {
status = -ENOMEM;
goto cleanup_return;
}
diff --git a/path.c b/path.c
@@ -26,6 +26,9 @@ get_file_extension(memory_region *m, const path *p)
{
char *cpy = rstrdup(m, p);
char *last_dot = strrchr(cpy, '.');
+ if (last_dot) {
+ last_dot++;
+ }
return last_dot;
}