agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit de45ebdb7c33f7439ef1c86929f40e2545d6bdae
parent 0ece7056243b3754af702545dd53a0013aeb72dd
Author: Marc Coquand <marc@coquand.email>
Date: Thu, 26 Jun 2025 11:12:38 +0100
Broken directory support
Diffstat:
| M | journal_entry.c | | | 154 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
| M | journal_entry.h | | | 6 | ++++++ |
| M | main.c | | | 59 | +++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
3 files changed, 206 insertions(+), 13 deletions(-)
diff --git a/journal_entry.c b/journal_entry.c
@@ -21,6 +21,8 @@
char ICS_DIR[256];
+static const char *IS_DIRECTORY_PROPERTY = "X-CALDAVFS-ISDIRECTORY";
+
// tree of journal entries
struct tree_node *fuse_tree_root = NULL;
@@ -189,10 +191,57 @@ get_entry_from_fuse_path(const char *path)
return entry;
}
+// If it has a relationship type
+bool
+is_directory_component(icalcomponent *component)
+{
+ icalcomponent *journal = icalcomponent_get_first_component(
+ component, ICAL_VJOURNAL_COMPONENT);
+
+ for (icalproperty *prop = icalcomponent_get_first_property(
+ journal, ICAL_RELATEDTO_PROPERTY);
+ prop != NULL; prop = icalcomponent_get_next_property(
+ journal, ICAL_RELATEDTO_PROPERTY)) {
+
+ const char *reltype =
+ icalproperty_get_parameter_as_string(prop, "RELTYPE");
+ if (reltype && strcasecmp(reltype, "PARENT") == 0) {
+ return true;
+ }
+ }
+ for (icalproperty *prop =
+ icalcomponent_get_first_property(journal, ICAL_X_PROPERTY);
+ prop != NULL;
+ prop = icalcomponent_get_next_property(journal, ICAL_X_PROPERTY)) {
+ LOG("FOUND X PROPERTY");
+
+ const char *prop_name = icalproperty_get_x_name(prop);
+ if (prop_name &&
+ strcmp(prop_name, IS_DIRECTORY_PROPERTY) == 0) {
+ const char *value =
+ icalproperty_get_value_as_string(prop);
+ LOG("FOUND DIRECTORY");
+ return value && strcmp(value, "YES") == 0;
+ }
+ }
+ return false;
+}
+
+// We assume it to be a directory if it has a property directory property
+// so or is a parent node
bool
entry_is_directory(tree_node *node)
{
- return (node->child_count > 0);
+ if (!node->data) {
+ return true;
+ }
+ else if (node->child_count > 0) {
+ return true;
+ }
+ else {
+ struct journal_entry *entry = node->data;
+ return is_directory_component(entry->component);
+ }
}
char *
@@ -237,7 +286,7 @@ parse_ics_file(const char *filename)
return component;
}
-// We assume that there is only one entry in the file
+// We assume that there is only one inner component in the file
int
parse_ics_to_journal_entry_component(const char *filename,
struct journal_entry *entry)
@@ -300,8 +349,16 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
// Two entries are created on the same minute
// TODO: Allow custom fileformats
// TODO: Check for duplicates
- if (asprintf(&entry->filename, "%s.txt",
- icalcomponent_get_summary(entry->component)) == -1) {
+ if (is_directory_component(entry->component)) {
+ if (asprintf(&entry->filename, "%s",
+ icalcomponent_get_summary(entry->component)) ==
+ -1) {
+ LOG("Failed to write filename");
+ return -1;
+ };
+ }
+ else if (asprintf(&entry->filename, "%s.txt",
+ icalcomponent_get_summary(entry->component)) == -1) {
LOG("Failed to write filename");
return -1;
};
@@ -594,6 +651,39 @@ create_vjournal_entry(char *summary)
return calendar;
}
+icalcomponent *
+create_vjournal_directory(char *summary)
+{
+
+ icalcomponent *calendar = icalcomponent_new_vcalendar();
+
+ icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
+ icalcomponent_add_property(calendar,
+ icalproperty_new_prodid("-//caldavfs//EN"));
+
+ // Step 2: Create a VJOURNAL entry
+ icalcomponent *journal = icalcomponent_new_vjournal();
+ char *id = create_new_unique_ics_uid();
+
+ icalcomponent_add_property(journal, icalproperty_new_uid(id));
+
+ icalcomponent_add_property(
+ journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
+ icaltimezone_get_utc_timezone())));
+ icalcomponent_add_property(journal, icalproperty_new_summary(summary));
+
+ // Empty description; unused
+ icalcomponent_add_property(journal, icalproperty_new_description(""));
+
+ 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(journal, is_directory);
+
+ icalcomponent_add_component(calendar, journal);
+ return calendar;
+}
+
void
remove_file_extension(char *path)
{
@@ -638,13 +728,13 @@ find_by_component_uuid(tree_node *root, const char *target_uuid)
// TODO: Handle duplicates in filename
int
-create_entry_from_fuse(const char *filename)
+create_entry_from_fuse(const char *fuse_path)
{
const char *entry_prefix = "/";
- if (strncmp(filename, entry_prefix, strlen(entry_prefix)) != 0) {
+ if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
return -EINVAL;
}
- char *new_basename = strrchr(filename, '/');
+ char *new_basename = strrchr(fuse_path, '/');
new_basename++;
char *without_extension = strdup(new_basename);
@@ -688,6 +778,56 @@ create_entry_from_fuse(const char *filename)
return 0;
}
+int
+create_directory_from_fuse_path(const char *fuse_path)
+{
+ const char *entry_prefix = "/";
+ if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
+ return -EINVAL;
+ }
+ char *new_basename = strrchr(fuse_path, '/');
+ new_basename++;
+
+ struct journal_entry *new_entry =
+ xcalloc(1, sizeof(struct journal_entry));
+ icalcomponent *vjournal_component =
+ create_vjournal_directory(new_basename);
+
+ new_entry->component = vjournal_component;
+ new_entry->filename = strdup(new_basename);
+ if (asprintf(&new_entry->filename_original, "%s.ics",
+ icalcomponent_get_uid(new_entry->component)) == -1) {
+ LOG("Failed to create journal entry");
+ free_journal_entry(new_entry);
+
+ return -ENOMEM;
+ }
+ LOG("Inserting vjournal directory");
+
+ hashmap_insert(entries_original_ics, new_entry->filename_original,
+ new_entry);
+
+ // TODO: Subdirectory
+ add_child(fuse_tree_root, create_file_node(new_entry));
+
+ // Write back to the original
+ char *filepath = get_original_filepath(new_entry);
+ LOG("Got original filepath %s. From %s, and %s", filepath,
+ new_entry->filename_original, new_entry->filename);
+
+ if (write_entry_to_ical_file(new_entry) != 0) {
+ LOG("Write to entry failed");
+ free(filepath);
+ return -EIO;
+ }
+
+ free(filepath);
+
+ LOG("File updated");
+ return 0;
+}
+
+// TODO: Add return status
void
update_or_create_fuse_entry_from_original(const char *filename)
{
diff --git a/journal_entry.h b/journal_entry.h
@@ -100,6 +100,9 @@ update_delete_from_original_path(const char *filepath);
int
load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry);
+icalcomponent *
+create_vjournal_directory(char *summary);
+
// Initializes
// entries_original_key
// entries_fuse_key
@@ -135,6 +138,9 @@ create_new_unique_ics_uid();
icalcomponent *
create_vjournal_entry(char *summary);
+int
+create_directory_from_fuse_path(const char *fuse_path);
+
void
remove_file_extension(char *path);
diff --git a/main.c b/main.c
@@ -1,4 +1,5 @@
#include "tree.h"
+#include <cerrno>
#define FUSE_USE_VERSION 31
#include "hashmap.h"
@@ -99,13 +100,45 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
filler(buf, "..", NULL, 0, 0);
// If we are in the root directory, expose the "journal" directory
- // TODO: Subdirectories
if (strcmp(path, "/") == 0) {
- size_t n_keys = fuse_tree_root->child_count;
- for (size_t i = 0; i < n_keys; i++) {
- struct journal_entry *entry =
- fuse_tree_root->children[i]->data;
- filler(buf, entry->filename, NULL, 0, 0);
+ for (size_t i = 0; i < fuse_tree_root->child_count; i++) {
+ tree_node *node = fuse_tree_root->children[i];
+ struct journal_entry *entry = node->data;
+
+ struct stat st = {0};
+ // TODO: Fix
+ st.st_ino = i + 1;
+ st.st_mode = S_IFREG | 0744;
+
+ if (entry_is_directory(node)) {
+ st.st_mode = S_IFDIR | 0755;
+ }
+
+ filler(buf, entry->filename, &st, 0, 0);
+ }
+ }
+ else {
+ tree_node *node = find_node_by_path(fuse_tree_root, path);
+ if (node) {
+ for (size_t i = 0; i < node->child_count; i++) {
+ tree_node *child = node->children[i];
+ struct journal_entry *entry = child->data;
+
+ struct stat st = {0};
+ // TODO: Fix
+ st.st_ino = i + 1;
+ st.st_mode = S_IFREG | 0744;
+
+ if (entry_is_directory(child)) {
+ st.st_mode = S_IFDIR | 0755;
+ }
+
+ filler(buf, entry->filename, &st, 0, 0);
+ }
+ }
+ else {
+ pthread_mutex_unlock(&entries_mutex);
+ return -ENOENT;
}
}
pthread_mutex_unlock(&entries_mutex);
@@ -123,6 +156,19 @@ journal_open(const char *path, struct fuse_file_info *fi)
}
int
+journal_mkdir(const char *path, mode_t mode)
+{
+ pthread_mutex_lock(&entries_mutex);
+ LOG("MKDIR %s", path);
+ if (create_directory_from_fuse_path(path) != 0) {
+ pthread_mutex_unlock(&entries_mutex);
+ return -EIO;
+ };
+ pthread_mutex_unlock(&entries_mutex);
+ return 0;
+}
+
+int
journal_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
@@ -329,6 +375,7 @@ static const struct fuse_operations journal_oper = {.getattr = journal_getattr,
.utimens = journal_utimens,
.write = journal_write,
.create = journal_create,
+ .mkdir = journal_mkdir,
.unlink = journal_unlink,
.readlink =
journal_readlink,