agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 6a9c66a5235d7948ba935e1643b0ebc1be02880c
parent de45ebdb7c33f7439ef1c86929f40e2545d6bdae
Author: Marc Coquand <marc@coquand.email>
Date: Thu, 26 Jun 2025 21:30:06 +0100
Bugfixes
Diffstat:
3 files changed, 194 insertions(+), 73 deletions(-)
diff --git a/journal_entry.c b/journal_entry.c
@@ -61,6 +61,30 @@ get_node_component(tree_node *node)
return entry->component;
}
+int
+get_parent_path(const char *path, char **buffer)
+{
+ char *path_copy = strdup(path);
+
+ char *last_slash = strrchr(path_copy, '/');
+ if (last_slash == NULL) {
+ free(path_copy);
+ return 0;
+ }
+
+ // If they are the same, it means the parent is root
+ if (strcmp(last_slash, path_copy) == 0) {
+ int result = asprintf(buffer, "/");
+ free(path_copy);
+ return result;
+ }
+ *last_slash = '\0';
+ int result = asprintf(buffer, "%s", path_copy);
+ free(path_copy);
+
+ return result;
+}
+
tree_node **
get_node_children(tree_node *node)
{
@@ -68,6 +92,21 @@ get_node_children(tree_node *node)
return entries;
}
+tree_node *
+get_node_by_uuid(const char *target_uuid)
+{
+ char *filename_original = NULL;
+
+ // Filenames are uid.ics, so we levarage that
+ if (asprintf(&filename_original, "%s.ics", target_uuid) == 0) {
+ LOG("EMEM");
+ exit(1);
+ }
+
+ tree_node *node = hashmap_get(entries_original_ics, filename_original);
+ return node;
+}
+
char **
split_path(const char *path, size_t *count_out)
{
@@ -91,6 +130,45 @@ split_path(const char *path, size_t *count_out)
}
void
+set_parent_child_relationship_to_component(icalcomponent *parent,
+ icalcomponent *child)
+{
+ icalproperty *child_related_to_parent =
+ icalproperty_new_relatedto(icalcomponent_get_uid(parent));
+ icalparameter *reltype_child =
+ icalparameter_new_reltype(ICAL_RELTYPE_PARENT);
+ icalproperty_add_parameter(child_related_to_parent, reltype_child);
+ icalcomponent_add_property(child, child_related_to_parent);
+
+ icalproperty *parent_related_to_child =
+ icalproperty_new_relatedto(icalcomponent_get_uid(child));
+ icalparameter *reltype_parent =
+ icalparameter_new_reltype(ICAL_RELTYPE_CHILD);
+ icalproperty_add_parameter(parent_related_to_child, reltype_parent);
+
+ icalcomponent_add_property(parent, parent_related_to_child);
+}
+void
+add_child_to_node(tree_node *parent, tree_node *child)
+{
+ // Root node, don't add relationship to component
+ if (parent->data == NULL) {
+ add_child(parent, child);
+ return;
+ }
+ else {
+ struct journal_entry *p_entry = parent->data;
+ struct journal_entry *c_entry = child->data;
+ set_parent_child_relationship_to_component(p_entry->component,
+ c_entry->component);
+ write_entry_to_ical_file(p_entry);
+ write_entry_to_ical_file(c_entry);
+
+ add_child(parent, child);
+ }
+}
+
+void
free_segments(char **segments, size_t count)
{
for (size_t i = 0; i < count; ++i)
@@ -99,21 +177,28 @@ free_segments(char **segments, size_t count)
}
struct tree_node *
-find_node_by_path(struct tree_node *root, const char *path)
+get_node_by_path(struct tree_node *root, const char *path)
{
- if (!root || !path || *path == '\0')
+ if (!path)
return NULL;
+ if (strcmp(path, "/") == 0) {
+ LOG("Is ROOT %s", path);
+ return fuse_tree_root;
+ }
+
size_t count;
char **segments = split_path(path, &count);
if (!segments)
- return NULL;
+ return fuse_tree_root;
+ LOG("Segments are defined");
tree_node *current = root;
for (size_t i = 0; i < count && current; ++i) {
const char *segment = segments[i];
tree_node *next = NULL;
+ LOG("Segment: %s", segment);
for (size_t j = 0; j < current->child_count; ++j) {
const char *child_name =
@@ -177,7 +262,7 @@ get_entry_from_fuse_path(const char *path)
LOG("Invalid path: '%s'\n", path ? path : "NULL");
return NULL;
}
- tree_node *node = find_node_by_path(fuse_tree_root, path);
+ tree_node *node = get_node_by_path(fuse_tree_root, path);
if (!node || !node->data) {
LOG("No entry found for key: '%s'\n", path);
@@ -205,7 +290,7 @@ is_directory_component(icalcomponent *component)
const char *reltype =
icalproperty_get_parameter_as_string(prop, "RELTYPE");
- if (reltype && strcasecmp(reltype, "PARENT") == 0) {
+ if (reltype && strcasecmp(reltype, "CHILD") == 0) {
return true;
}
}
@@ -347,7 +432,6 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
// Make sure filename, which happens when
// Two entries are created on the same minute
- // TODO: Allow custom fileformats
// TODO: Check for duplicates
if (is_directory_component(entry->component)) {
if (asprintf(&entry->filename, "%s",
@@ -357,6 +441,9 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
return -1;
};
}
+
+ // TODO: Enable custom fileformats by storing them in a
+ // X-CALDAVFS-EXTENSION format
else if (asprintf(&entry->filename, "%s.txt",
icalcomponent_get_summary(entry->component)) == -1) {
LOG("Failed to write filename");
@@ -368,10 +455,25 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
return 0;
}
-// Initializes
-// entries_original_key
-// entries_fuse_key
-// Should only be run once
+const char *
+get_parent_uid(icalcomponent *component)
+{
+ for (icalproperty *prop = icalcomponent_get_first_property(
+ component, ICAL_RELATEDTO_PROPERTY);
+ prop != NULL; prop = icalcomponent_get_next_property(
+ component, ICAL_RELATEDTO_PROPERTY)) {
+ const char *reltype =
+ icalproperty_get_parameter_as_string(prop, "RELTYPE");
+ if (reltype && strcasecmp(reltype, "PARENT") == 0) {
+ return icalproperty_get_value_as_string(prop);
+ }
+ }
+
+ return NULL;
+}
+
+// Initializes values
+// Should only be run once at the start
void
load_journal_entries()
{
@@ -396,6 +498,7 @@ load_journal_entries()
}
struct dirent *entry;
+ // First pass: Store everything in root tree, fill up hashmap
while ((entry = readdir(dir))) {
if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
struct journal_entry *new_entry =
@@ -408,13 +511,12 @@ load_journal_entries()
continue;
}
- // TODO: Check for duplicate
tree_node *new_node = create_file_node(new_entry);
if (add_child(fuse_tree_root, new_node) != 0 ||
hashmap_insert(entries_original_ics,
new_entry->filename_original,
- new_entry) != 0) {
+ new_node) != 0) {
LOG("Failed to insert entry");
free_journal_entry(new_entry);
continue;
@@ -422,6 +524,28 @@ load_journal_entries()
}
}
closedir(dir);
+ LOG("Setting up directories");
+
+ // Second pass, reattach according to parent-child
+ for (size_t i = 0; i < fuse_tree_root->child_count; i++) {
+ tree_node *child = fuse_tree_root->children[i];
+ struct journal_entry *entry = child->data;
+
+ const char *parent_uid = get_parent_uid(entry->component);
+ if (parent_uid) {
+ tree_node *parent = get_node_by_uuid(parent_uid);
+ if (parent) {
+ detach_tree_node(child);
+ add_child(parent, child);
+ }
+ else {
+ LOG("COULD NOT FIND PARENT NODE: %s",
+ parent_uid);
+ }
+ }
+ }
+ LOG("Done");
+ // TODO: Handle duplicates by changing file name to .1, .2 etc.
}
size_t
@@ -572,7 +696,7 @@ do_journal_entry_rename(const char *old, const char *new)
*dot = '\0';
}
LOG("Summary is now %s", new_summary);
- tree_node *entry_node = find_node_by_path(fuse_tree_root, old);
+ tree_node *entry_node = get_node_by_path(fuse_tree_root, old);
struct journal_entry *old_entry = entry_node->data;
if (!old_entry) {
@@ -598,7 +722,7 @@ do_journal_entry_rename(const char *old, const char *new)
LOG("Inserting %s", new_entry->filename_original);
hashmap_insert(entries_original_ics, new_entry->filename_original,
- new_entry);
+ entry_node);
entry_node->data = new_entry;
free_journal_entry(old_entry);
@@ -704,28 +828,6 @@ match_by_uid(icalcomponent *component, const char *target_uuid)
return false;
}
-// TODO: Optimize
-tree_node *
-find_by_component_uuid(tree_node *root, const char *target_uuid)
-{
- if (!root)
- return NULL;
-
- struct journal_entry *entry = root->data;
- if (entry && entry->component &&
- match_by_uid(entry->component, target_uuid)) {
- return root;
- }
-
- for (size_t i = 0; i < root->child_count; ++i) {
- tree_node *found =
- find_by_component_uuid(root->children[i], target_uuid);
- if (found)
- return found;
- }
- return NULL;
-}
-
// TODO: Handle duplicates in filename
int
create_entry_from_fuse(const char *fuse_path)
@@ -756,24 +858,34 @@ create_entry_from_fuse(const char *fuse_path)
}
LOG("Inserting vjournal entry");
- hashmap_insert(entries_original_ics, new_entry->filename_original,
- new_entry);
+ tree_node *new_node = create_file_node(new_entry);
+ char *parent_path = NULL;
+ if (get_parent_path(fuse_path, &parent_path) == 0) {
+ add_child_to_node(fuse_tree_root, new_node);
+ }
+ else {
+ tree_node *parent =
+ get_node_by_path(fuse_tree_root, parent_path);
+ free(parent_path);
- add_child(fuse_tree_root, create_file_node(new_entry));
+ if (!parent || !entry_is_directory(parent)) {
- // 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);
+ return -ENOENT;
+ }
+ else if (!entry_is_directory(parent)) {
+ return -ENOTDIR;
+ }
+ add_child_to_node(parent, new_node);
+ }
+
+ hashmap_insert(entries_original_ics, new_entry->filename_original,
+ new_node);
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;
}
@@ -804,26 +916,33 @@ create_directory_from_fuse_path(const char *fuse_path)
}
LOG("Inserting vjournal directory");
- hashmap_insert(entries_original_ics, new_entry->filename_original,
- new_entry);
+ tree_node *new_node = create_file_node(new_entry);
- // TODO: Subdirectory
- add_child(fuse_tree_root, create_file_node(new_entry));
+ char *parent_path = NULL;
+ get_parent_path(fuse_path, &parent_path);
+ LOG("Parent path is %s", parent_path);
+ tree_node *parent = get_node_by_path(fuse_tree_root, parent_path);
+ free(parent_path);
- // 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 (!parent) {
+ LOG("Parent is not node");
+ return -ENOENT;
+ }
+ if (!entry_is_directory(parent)) {
+ LOG("Parent is not directory ");
+
+ return -ENOTDIR;
+ }
+ add_child_to_node(parent, new_node);
+ hashmap_insert(entries_original_ics, new_entry->filename_original,
+ new_node);
if (write_entry_to_ical_file(new_entry) != 0) {
LOG("Write to entry failed");
- free(filepath);
return -EIO;
}
- free(filepath);
-
- LOG("File updated");
+ LOG("DIRECTORY CREATED");
return 0;
}
@@ -846,20 +965,21 @@ update_or_create_fuse_entry_from_original(const char *filename)
if (!target_uuid) {
return;
}
- hashmap_insert(entries_original_ics, new_entry->filename_original,
- new_entry);
// TODO: Handle directories
- tree_node *existing_node =
- find_by_component_uuid(fuse_tree_root, target_uuid);
+ tree_node *existing_node = get_node_by_uuid(target_uuid);
// TODO: Enable subdirectories
if (existing_node) {
update_node_data(existing_node, new_entry);
}
else {
-
- add_child(fuse_tree_root, create_file_node(new_entry));
+ tree_node *new_node = create_file_node(new_entry);
+ // TODO: Look for child-parent relationship and update
+ // accordingly
+ add_child(fuse_tree_root, new_node);
+ hashmap_insert(entries_original_ics,
+ new_entry->filename_original, new_node);
}
LOG("File updated");
@@ -870,12 +990,11 @@ int
delete_from_fuse_path(const char *filepath)
{
int res = 0;
- tree_node *node = find_node_by_path(fuse_tree_root, filepath);
+ tree_node *node = get_node_by_path(fuse_tree_root, filepath);
if (!node) {
return -EIO;
}
struct journal_entry *entry = get_entry(node);
- const char *entry_uuid = icalcomponent_get_uid(entry->component);
char *filepath_original = get_original_filepath(entry);
if (!filepath_original) {
LOG("Entry not found");
@@ -885,7 +1004,7 @@ delete_from_fuse_path(const char *filepath)
res = remove(filepath);
- hashmap_remove(entries_original_ics, entry_uuid);
+ hashmap_remove(entries_original_ics, filepath_original);
// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
// Instead, update the children so they have a new parent
@@ -908,7 +1027,8 @@ update_delete_from_original_path(const char *filepath)
return -EIO;
}
- tree_node *node = find_by_component_uuid(fuse_tree_root, filepath);
+ // TODO: Fix so it finds by actual UUID, not filepath
+ tree_node *node = get_node_by_uuid(filepath);
// TODO: Subdirectories SHOULD NOT BE REMOVED HERE
// Instead, update the children so they have a new parent
diff --git a/journal_entry.h b/journal_entry.h
@@ -127,6 +127,9 @@ get_last_modified(struct journal_entry *entry);
int
build_today_hashmap();
+struct tree_node *
+get_node_by_path(struct tree_node *root, const char *path);
+
int
build_notes_hashmap();
diff --git a/main.c b/main.c
@@ -1,9 +1,8 @@
-#include "tree.h"
-#include <cerrno>
#define FUSE_USE_VERSION 31
#include "hashmap.h"
#include "journal_entry.h"
+#include "tree.h"
#include "util.h"
#include <dirent.h>
#include <errno.h>
@@ -43,8 +42,7 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
goto unlock_and_return;
}
- // TODO: Handle directories
- tree_node *node = find_node_by_path(fuse_tree_root, path);
+ tree_node *node = get_node_by_path(fuse_tree_root, path);
if (!node) {
LOG("Entry not found");
ret_code = -ENOENT;
@@ -118,7 +116,7 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
}
}
else {
- tree_node *node = find_node_by_path(fuse_tree_root, path);
+ tree_node *node = get_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];