agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 25041ea993bd7df5e786e55ec442676b22c284f5
parent 417be015e6aa1bccb5e3e4b4834eecaf5373f77d
Author: Marc Coquand <marc@coquand.email>
Date: Wed, 25 Jun 2025 20:50:18 +0100
Overhaul the project, use tree structure instead
Diffstat:
| M | Makefile | | | 8 | ++++---- |
| M | journal_entry.c | | | 443 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
| M | journal_entry.h | | | 26 | +++++++++++++++++--------- |
| M | main.c | | | 243 | +++++++++---------------------------------------------------------------------- |
| D | sample_ics/2025-01-01.ics | | | 8 | -------- |
| A | tree.c | | | 106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | tree.h | | | 38 | ++++++++++++++++++++++++++++++++++++++ |
7 files changed, 465 insertions(+), 407 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,11 +5,11 @@ LIBS = `pkg-config uuid fuse3 libical --cflags --libs`
all: caldavfs
-caldavfs: main.c util.c hashmap.c journal_entry.c
- $(CC) $(CFLAGS) main.c journal_entry.c util.c hashmap.c -o mount_caldavfs $(LIBS)
+caldavfs: main.c util.c hashmap.c journal_entry.c tree.c
+ $(CC) $(CFLAGS) *.c -o mount_caldavfs $(LIBS)
-debug: main.c util.c hashmap.c journal_entry.c
- $(CC) $(CFLAGS_DEBUG) $(DEBUG_FLAGS) main.c journal_entry.c util.c hashmap.c -o mount_caldavfs-debug $(LIBS)
+debug: main.c util.c hashmap.c journal_entry.c tree.c
+ $(CC) $(CFLAGS_DEBUG) $(DEBUG_FLAGS) *.c -o mount_caldavfs-debug $(LIBS)
clean:
rm -f caldavfs caldavfs-debug
diff --git a/journal_entry.c b/journal_entry.c
@@ -1,6 +1,7 @@
#include "journal_entry.h"
#include "hashmap.h"
+#include "tree.h"
#include "util.h"
#include <dirent.h>
#include <errno.h>
@@ -18,17 +19,115 @@
#include <uuid/uuid.h>
#include <wordexp.h>
-struct hashmap *entries_fuse = NULL;
-
char ICS_DIR[256];
+// tree of journal entries
+struct tree_node *fuse_tree_root = NULL;
+
// journal_entry
struct hashmap *entries_original_ics = NULL;
-// string -> string
-struct hashmap *entries_today = NULL;
+struct tree_node *
+create_file_node(struct journal_entry *entry)
+{
+ struct tree_node *file_node =
+ create_tree_node(entry, free_journal_entry);
+ return file_node;
+}
+struct journal_entry *
+get_entry(tree_node *node)
+{
+ struct journal_entry *entry = node->data;
+ return entry;
+}
+
+char *
+get_node_filename(tree_node *node)
+{
+ struct journal_entry *entry = node->data;
+ if (entry == NULL)
+ return "";
+ return entry->filename;
+}
+
+icalcomponent *
+get_node_component(tree_node *node)
+{
+ struct journal_entry *entry = node->data;
+ if (entry == NULL)
+ return NULL;
+ return entry->component;
+}
+
+tree_node **
+get_node_children(tree_node *node)
+{
+ tree_node **entries = node->children;
+ return entries;
+}
+
+char **
+split_path(const char *path, size_t *count_out)
+{
+ char *path_copy = strdup(path);
+ if (!path_copy)
+ return NULL;
+
+ size_t count = 0;
+ char **segments = NULL;
+
+ char *token = strtok(path_copy, "/");
+ while (token) {
+ segments = xreallocarray(segments, sizeof(char *), (count + 1));
+ segments[count++] = strdup(token);
+ token = strtok(NULL, "/");
+ }
+ free(path_copy);
+
+ *count_out = count;
+ return segments;
+}
+
+void
+free_segments(char **segments, size_t count)
+{
+ for (size_t i = 0; i < count; ++i)
+ free(segments[i]);
+ free(segments);
+}
+
+struct tree_node *
+find_node_by_path(struct tree_node *root, const char *path)
+{
+ if (!root || !path || *path == '\0')
+ return NULL;
+
+ size_t count;
+ char **segments = split_path(path, &count);
+ if (!segments)
+ return NULL;
+
+ tree_node *current = root;
+
+ for (size_t i = 0; i < count && current; ++i) {
+ const char *segment = segments[i];
+ tree_node *next = NULL;
+
+ for (size_t j = 0; j < current->child_count; ++j) {
+ const char *child_name =
+ get_node_filename(current->children[j]);
+ if (strcmp(child_name, segment) == 0) {
+ next = current->children[j];
+ break;
+ }
+ }
+ current = next;
+ }
+
+ free_segments(segments, count);
+ return current;
+}
-struct hashmap *entries_notes = NULL;
// Full filepath of original ics file
char *
get_original_filepath(const struct journal_entry *entry)
@@ -76,37 +175,26 @@ 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);
- const char *prefix = "/entries/";
- size_t prefix_len = strlen(prefix);
-
- if (strncmp(path, prefix, prefix_len) != 0) {
- LOG("Path does not start with '/entries/': '%s'\n", path);
- return NULL;
- }
-
- // Extract the part after "/entries/"
- const char *key = path + prefix_len;
-
- // If key is empty (i.e., path is exactly "/entries/"), reject
- if (strlen(key) == 0) {
- LOG("Empty key in path: '%s'\n", path);
+ if (!node || !node->data) {
+ LOG("No entry found for key: '%s'\n", path);
return NULL;
}
- LOG("Looking up key: '%s'", key);
- struct journal_entry *entry = hashmap_get(entries_fuse, key);
-
- if (!entry) {
- LOG("No entry found for key: '%s'\n", key);
- return NULL;
- }
+ struct journal_entry *entry = node->data;
LOG("Entry found: filename = '%s', original = '%s', path = '%s'",
entry->filename, entry->filename_original, path);
return entry;
}
+bool
+entry_is_directory(tree_node *node)
+{
+ return (node->child_count > 0);
+}
+
char *
read_stream(char *s, size_t size, void *d)
{
@@ -128,10 +216,11 @@ icalcomponent *
parse_ics_file(const char *filename)
{
icalcomponent *component = NULL;
+
+ icalparser *parser = icalparser_new();
FILE *stream = fopen(filename, "r");
assert(stream != 0);
- icalparser *parser = icalparser_new();
icalparser_set_gen_data(parser, stream);
char *line;
@@ -210,6 +299,7 @@ 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 (asprintf(&entry->filename, "%s.txt",
icalcomponent_get_summary(entry->component)) == -1) {
LOG("Failed to write filename");
@@ -233,12 +323,12 @@ load_journal_entries()
hashmap_free(entries_original_ics);
entries_original_ics = NULL;
}
- if (entries_fuse) {
- hashmap_free(entries_fuse);
- entries_fuse = NULL;
+ if (fuse_tree_root) {
+ free_tree(fuse_tree_root);
+ fuse_tree_root = NULL;
}
entries_original_ics = hashmap_new(NULL);
- entries_fuse = hashmap_new(free_journal_entry);
+ fuse_tree_root = create_tree_node(NULL, NULL);
LOG("Loading journal entries from: %s\n", ICS_DIR);
@@ -261,8 +351,10 @@ load_journal_entries()
continue;
}
- if (hashmap_insert(entries_fuse, new_entry->filename,
- new_entry) != 0 ||
+ // 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) {
@@ -317,129 +409,6 @@ get_last_modified(struct journal_entry *entry)
}
}
-// TODO: Probably better to calculate this on demand
-int
-build_today_hashmap()
-{
- if (entries_today != NULL)
- hashmap_free(entries_today);
- entries_today = hashmap_new(free);
-
- size_t n_keys = 0;
- char **keys = hashmap_get_keys(entries_fuse, &n_keys);
-
- if (!keys) {
- LOG("Could not get keys");
- return -1;
- }
-
- for (size_t i = 0; i < n_keys; i++) {
- struct journal_entry *entry =
- hashmap_get(entries_fuse, keys[i]);
- if (!entry)
- continue;
-
- // Get the dtstamp of the first icalcomponent
- icalcomponent *component = entry->component;
- if (!component)
- continue;
-
- struct icaltimetype dt = icalcomponent_get_dtstamp(component);
-
- if (icaltime_compare_date_only(dt, icaltime_today()) == 0) {
- const char *summary =
- icalcomponent_get_summary(component);
- if (summary == NULL) {
- LOG("SUMMARY IS NULL");
- continue;
- }
- char *filename;
-
- asprintf(&filename, "%s", summary);
-
- if (filename == NULL) {
- LOG("Could not get filename");
- continue;
- }
-
- char *dest;
-
- if (asprintf(&dest, "../entries/%s", entry->filename) ==
- -1)
- return -1;
-
- hashmap_insert(entries_today, filename, dest);
- }
- }
- LOG("Build complete");
-
- hashmap_free_keys(keys, n_keys);
- return 0;
-}
-
-int
-build_notes_hashmap()
-{
- if (entries_notes != NULL)
- hashmap_free(entries_notes);
- entries_notes = hashmap_new(free);
- LOG("BUILDING ENTRIES");
-
- size_t n_keys = 0;
- char **keys = hashmap_get_keys(entries_fuse, &n_keys);
-
- if (!keys) {
- LOG("Could not get keys");
- return -1;
- }
- LOG("GOT ENTRIES");
-
- for (size_t i = 0; i < n_keys; i++) {
- struct journal_entry *entry =
- hashmap_get(entries_fuse, keys[i]);
-
- LOG("Looking up entry");
- if (!entry)
- continue;
-
- // Get the dtstamp of the first icalcomponent
- icalcomponent *component = entry->component;
- if (!component)
- continue;
-
- icalcomponent_kind type =
- icalcomponent_isa(icalcomponent_get_inner(component));
-
- if (type == ICAL_VJOURNAL_COMPONENT) {
- const char *summary =
- icalcomponent_get_summary(component);
- if (summary == NULL) {
- LOG("SUMMARY IS NULL");
- continue;
- }
- char *filename;
- asprintf(&filename, "%s", summary);
-
- if (filename == NULL) {
- LOG("Could not get filename");
- continue;
- }
-
- char *dest;
-
- if (asprintf(&dest, "../entries/%s", entry->filename) ==
- -1)
- return -1;
-
- hashmap_insert(entries_notes, filename, dest);
- }
- }
- LOG("Build complete");
-
- hashmap_free_keys(keys, n_keys);
- return 0;
-}
-
struct journal_entry *
copy_journal_entry(const struct journal_entry *src)
{
@@ -546,8 +515,9 @@ 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);
+ struct journal_entry *old_entry = entry_node->data;
- struct journal_entry *old_entry = get_entry_from_fuse_path(old);
if (!old_entry) {
LOG("Entry not found '%s'", old);
free(new_copy);
@@ -573,11 +543,8 @@ do_journal_entry_rename(const char *old, const char *new)
hashmap_insert(entries_original_ics, new_entry->filename_original,
new_entry);
- hashmap_insert(entries_fuse, new_entry->filename, new_entry);
-
- // Insert original ics first to avoid use after free
- // Insert frees older entry
- hashmap_remove(entries_fuse, old_entry->filename);
+ entry_node->data = new_entry;
+ free_journal_entry(old_entry);
write_entry_to_ical_file(new_entry);
free(new_copy);
@@ -639,6 +606,88 @@ remove_file_extension(char *path)
}
}
+bool
+match_by_uid(icalcomponent *component, const char *target_uuid)
+{
+ const char *uid = icalcomponent_get_uid(component);
+ return uid && strcmp(uid, target_uuid) == 0;
+ 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 *filename)
+{
+ const char *entry_prefix = "/";
+ if (strncmp(filename, entry_prefix, strlen(entry_prefix)) != 0) {
+ return -EINVAL;
+ }
+ char *new_basename = strrchr(filename, '/');
+ new_basename++;
+
+ char *without_extension = strdup(new_basename);
+ remove_file_extension(without_extension);
+
+ struct journal_entry *new_entry =
+ xcalloc(1, sizeof(struct journal_entry));
+ icalcomponent *vjournal_component =
+ create_vjournal_entry(without_extension);
+
+ 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 entry");
+
+ hashmap_insert(entries_original_ics, new_entry->filename_original,
+ new_entry);
+
+ 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;
+}
+
void
update_or_create_fuse_entry_from_original(const char *filename)
{
@@ -653,22 +702,80 @@ update_or_create_fuse_entry_from_original(const char *filename)
free_journal_entry(new_entry);
return;
}
+ const char *target_uuid = icalcomponent_get_uid(new_entry->component);
+ if (!target_uuid) {
+ return;
+ }
hashmap_insert(entries_original_ics, new_entry->filename_original,
new_entry);
- hashmap_insert(entries_fuse, new_entry->filename, new_entry);
+
+ // TODO: Handle directories
+ tree_node *existing_node =
+ find_by_component_uuid(fuse_tree_root, 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));
+ }
LOG("File updated");
}
+// Deletes original file too!
+int
+delete_from_fuse_path(const char *filepath)
+{
+ int res = 0;
+ tree_node *node = find_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");
+ return -EIO;
+ }
+ LOG("Deleting file %s", filepath);
+
+ res = remove(filepath);
+
+ hashmap_remove(entries_original_ics, entry_uuid);
+
+ // TODO: Subdirectories SHOULD NOT BE REMOVED HERE
+ // Instead, update the children so they have a new parent
+ // which is the parent node.
+ // Requires a change_parent function.
+ detach_tree_node(node);
+ free_tree(node);
+ return res;
+}
+
int
-update_fuse_entry_delete(const char *file)
+update_delete_from_original_path(const char *filepath)
{
// Strip path, validate just the new basename
- const char *keyname = strrchr(file, '/');
+ const char *keyname = strrchr(filepath, '/');
keyname++;
- LOG("Found entry to delete");
+ struct journal_entry *entry =
+ hashmap_get(entries_original_ics, keyname);
+ if (!entry) {
+ return -EIO;
+ }
+
+ tree_node *node = find_by_component_uuid(fuse_tree_root, filepath);
+
+ // TODO: Subdirectories SHOULD NOT BE REMOVED HERE
+ // Instead, update the children so they have a new parent
+ // which is the parent node.
+ // Requires a change_parent function.
+ detach_tree_node(node);
+ free_tree(node);
hashmap_remove(entries_original_ics, keyname);
- // Frees
- hashmap_remove(entries_fuse, keyname);
return 0;
}
diff --git a/journal_entry.h b/journal_entry.h
@@ -36,19 +36,11 @@ struct journal_entry {
extern char ICS_DIR[256];
-// We create two maps that link to the same entries
-// journal_entry
-extern struct hashmap *entries_fuse;
+extern struct tree_node *fuse_tree_root;
// journal_entry
extern struct hashmap *entries_original_ics;
-// string -> string
-extern struct hashmap *entries_today;
-
-// string -> string
-extern struct hashmap *entries_notes;
-
// Full filepath of original ics file
char *
get_original_filepath(const struct journal_entry *entry);
@@ -87,6 +79,22 @@ load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry);
int
parse_ics_to_journal_entry_component(const char *filename,
struct journal_entry *entry);
+struct tree_node *
+find_node_by_path(struct tree_node *root, const char *path);
+
+struct journal_entry *
+get_entry(struct tree_node *node);
+
+bool
+entry_is_directory(struct tree_node *node);
+
+int
+delete_from_fuse_path(const char *filepath);
+
+int
+create_entry_from_fuse(const char *filename);
+int
+update_delete_from_original_path(const char *filepath);
// Returns -1 on failure, 0 on success
int
diff --git a/main.c b/main.c
@@ -1,3 +1,4 @@
+#include "tree.h"
#define FUSE_USE_VERSION 31
#include "hashmap.h"
@@ -40,59 +41,25 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
stbuf->st_nlink = 2;
goto unlock_and_return;
}
- else if (strcmp(path, "/entries") == 0) {
- stbuf->st_mode = S_IFDIR | 0444;
- stbuf->st_nlink = 2;
- goto unlock_and_return;
- }
- else if (strcmp(path, "/today") == 0) {
- stbuf->st_mode = S_IFDIR | 0444;
- stbuf->st_nlink = 2;
+
+ // TODO: Handle directories
+ tree_node *node = find_node_by_path(fuse_tree_root, path);
+ if (!node) {
+ LOG("Entry not found");
+ ret_code = -ENOENT;
goto unlock_and_return;
}
- else if (strcmp(path, "/notes") == 0) {
+
+ struct journal_entry *entry = get_entry(node);
+ if (entry_is_directory(node)) {
stbuf->st_mode = S_IFDIR | 0444;
stbuf->st_nlink = 2;
- goto unlock_and_return;
}
-
- const char *today_prefix = "/today/";
- if (strncmp(path, today_prefix, strlen(today_prefix)) == 0) {
- const char *key = path + strlen(today_prefix);
- char *entry_filename = hashmap_get(entries_today, key);
- if (!entry_filename) {
- ret_code = -ENOENT;
- goto unlock_and_return;
- }
-
- stbuf->st_mode = S_IFLNK | 0444;
+ else {
+ stbuf->st_mode = S_IFREG | 0774;
stbuf->st_nlink = 1;
- goto unlock_and_return;
}
- const char *notes_prefix = "/notes/";
- if (strncmp(path, notes_prefix, strlen(notes_prefix)) == 0) {
- const char *key = path + strlen(notes_prefix);
- char *entry_filename = hashmap_get(entries_notes, key);
- if (!entry_filename) {
- ret_code = -ENOENT;
- goto unlock_and_return;
- }
-
- stbuf->st_mode = S_IFLNK | 0444;
- stbuf->st_nlink = 1;
- goto unlock_and_return;
- }
-
- struct journal_entry *entry = get_entry_from_fuse_path(path);
- if (!entry) {
- LOG("Entry not found");
- ret_code = -ENOENT;
- goto unlock_and_return;
- }
- stbuf->st_mode = S_IFREG | 0774;
- stbuf->st_nlink = 1;
-
int size = get_entry_content_size(entry);
stbuf->st_size = (off_t)size;
@@ -117,49 +84,6 @@ int
journal_readlink(const char *path, char *buf, size_t size)
{
LOG("READLINK path: %s", path);
- const char *today_prefix = "/today/";
- if (strncmp(path, today_prefix, strlen(today_prefix)) == 0) {
- pthread_mutex_lock(&entries_mutex);
- const char *key = path + strlen(today_prefix);
- LOG("Lookup %s", key);
- char *link = hashmap_get(entries_today, key);
-
- if (!link) {
- LOG("Link not found for %s", key);
-
- pthread_mutex_unlock(&entries_mutex);
- return -ENOENT;
- }
- LOG("Link found %s", link);
-
- // Return symlink target
- snprintf(buf, size, "%s", link);
-
- pthread_mutex_unlock(&entries_mutex);
- return 0;
- }
- const char *notes_prefix = "/notes/";
- if (strncmp(path, notes_prefix, strlen(notes_prefix)) == 0) {
- pthread_mutex_lock(&entries_mutex);
- const char *key = path + strlen(notes_prefix);
- LOG("Lookup %s", key);
- char *link = hashmap_get(entries_notes, key);
-
- if (!link) {
- LOG("Link not found for %s", key);
-
- pthread_mutex_unlock(&entries_mutex);
- return -ENOENT;
- }
- LOG("Link found %s", link);
-
- // Return symlink target
- snprintf(buf, size, "%s", link);
-
- pthread_mutex_unlock(&entries_mutex);
- return 0;
- }
-
return -ENOENT;
}
@@ -175,58 +99,14 @@ 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) {
- filler(buf, "entries", NULL, 0, 0);
- filler(buf, "today", NULL, 0, 0);
- filler(buf, "notes", NULL, 0, 0);
- }
- else if (strcmp(path, "/entries") == 0) {
- size_t n_keys = 0;
- char **keys = hashmap_get_keys(entries_fuse, &n_keys);
- if (!keys) {
- pthread_mutex_unlock(&entries_mutex);
- return -ENOMEM;
- }
-
+ size_t n_keys = fuse_tree_root->child_count;
for (size_t i = 0; i < n_keys; i++) {
- filler(buf, keys[i], NULL, 0, 0);
+ struct journal_entry *entry =
+ fuse_tree_root->children[i]->data;
+ filler(buf, entry->filename, NULL, 0, 0);
}
-
- hashmap_free_keys(keys, n_keys);
- }
- else if (strcmp(path, "/today") == 0) {
- size_t n_keys = 0;
- LOG("BUILDING TODAY HASHMAP");
-
- build_today_hashmap();
-
- LOG("GETTING KEYS");
- char **keys = hashmap_get_keys(entries_today, &n_keys);
- if (!keys) {
- return -ENOMEM;
- }
- for (size_t i = 0; i < n_keys; i++) {
- filler(buf, keys[i], NULL, 0, 0);
- }
-
- hashmap_free_keys(keys, n_keys);
- }
- else if (strcmp(path, "/notes") == 0) {
- size_t n_keys = 0;
- LOG("BUILDING NOTES HASHMAP");
-
- build_notes_hashmap();
-
- LOG("GETTING KEYS");
- char **keys = hashmap_get_keys(entries_notes, &n_keys);
- if (!keys) {
- return -ENOMEM;
- }
- for (size_t i = 0; i < n_keys; i++) {
- filler(buf, keys[i], NULL, 0, 0);
- }
-
- hashmap_free_keys(keys, n_keys);
}
pthread_mutex_unlock(&entries_mutex);
return 0;
@@ -326,40 +206,13 @@ journal_unlink(const char *file)
LOG("DELETE %s", file);
int res = 0;
- // Strip path, validate just the new basename
- const char *keyname = strrchr(file, '/');
- keyname++;
-
- struct journal_entry *entry = hashmap_get(entries_fuse, keyname);
- if (!entry) {
- LOG("Entry does not exist");
- pthread_mutex_unlock(&entries_mutex);
- return -EIO;
- }
- LOG("Found entry to delete");
-
- char *filepath = get_original_filepath(entry);
- if (!filepath) {
- LOG("Entry not found");
- pthread_mutex_unlock(&entries_mutex);
- return -EIO;
- }
- LOG("Deleting file %s", filepath);
-
- res = remove(filepath);
+ res = delete_from_fuse_path(file);
if (res != 0) {
LOG("Failed to delete file");
pthread_mutex_unlock(&entries_mutex);
-
- free(filepath);
return -EIO;
}
- hashmap_remove(entries_original_ics, keyname);
- // Frees
- hashmap_remove(entries_fuse, keyname);
- free(filepath);
-
LOG("Delete successful");
pthread_mutex_unlock(&entries_mutex);
return 0;
@@ -370,13 +223,6 @@ journal_rename(const char *old, const char *new, unsigned int flags)
{
pthread_mutex_lock(&entries_mutex);
LOG("RENAME %s -> %s", old, new);
- if (strncmp(old, "/entries/", strlen("/entries/")) != 0 ||
- strncmp(new, "/entries/", strlen("/entries/")) != 0) {
-
- pthread_mutex_unlock(&entries_mutex);
- LOG("Invalid file names");
- return -ENOTSUP;
- }
int res = do_journal_entry_rename(old, new);
pthread_mutex_unlock(&entries_mutex);
@@ -388,48 +234,8 @@ journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
{
pthread_mutex_lock(&entries_mutex);
LOG("CREATE %s", filename);
-
- const char *entry_prefix = "/entries/";
- if (strncmp(filename, entry_prefix, strlen(entry_prefix)) != 0) {
- return -EINVAL;
- }
- char *new_basename = strrchr(filename, '/');
- new_basename++;
- char *without_extension = strdup(new_basename);
- remove_file_extension(without_extension);
-
- struct journal_entry *new_entry =
- xcalloc(1, sizeof(struct journal_entry));
- icalcomponent *vjournal_component =
- create_vjournal_entry(without_extension);
-
- 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 entry");
-
- hashmap_insert(entries_original_ics, new_entry->filename_original,
- new_entry);
- hashmap_insert(entries_fuse, new_entry->filename, 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);
-
+ // TODO: Handle mode and fuse_file_info
+ create_entry_from_fuse(filename);
pthread_mutex_unlock(&entries_mutex);
return 0;
}
@@ -480,7 +286,8 @@ watch_ics_dir(void *arg)
if (event->mask & IN_DELETE) {
pthread_mutex_lock(&entries_mutex);
- update_fuse_entry_delete(full_path);
+ update_delete_from_original_path(
+ full_path);
pthread_mutex_unlock(&entries_mutex);
}
else if (event->mask & IN_MODIFY) {
@@ -534,6 +341,8 @@ main(int argc, char *argv[])
tzset();
+ fuse_tree_root = create_tree_node(NULL, NULL);
+
if (load_caldavfs_environment() != 0) {
fprintf(stderr, "Failed to load ICS directory\n");
exit(1);
@@ -552,10 +361,8 @@ main(int argc, char *argv[])
pthread_join(watcher_thread, NULL); // Clean up
LOG("Pthread freed");
- hashmap_free(entries_fuse);
hashmap_free(entries_original_ics);
- hashmap_free(entries_today);
- hashmap_free(entries_notes);
+ free_tree(fuse_tree_root);
LOG("Hashmap freed");
LOG("Regexp freed");
diff --git a/sample_ics/2025-01-01.ics b/sample_ics/2025-01-01.ics
@@ -1,8 +0,0 @@
-BEGIN:VCALENDAR
-BEGIN:VJOURNAL
-UID:20250101T120000Z-001@host.com
-DTSTAMP:20250101T120000Z
-SUMMARY:New Year's Day Reflections
-DESCRIPTION:the past year and setting goals for the new one HELLO.\n\nHi world\n\n\n
-END:VJOURNAL
-END:VCA
diff --git a/tree.c b/tree.c
@@ -0,0 +1,106 @@
+
+#include "tree.h"
+#include "util.h"
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+tree_node *
+create_tree_node(void *data, void (*free_fn)(void *))
+{
+ tree_node *node = xmalloc(sizeof(tree_node));
+ if (!node)
+ return NULL;
+ node->data = data;
+ node->parent = NULL;
+ node->children = NULL;
+ node->child_count = 0;
+ node->child_capacity = 0;
+ node->free_fn = free_fn;
+
+ return node;
+}
+
+size_t
+add_child(tree_node *parent, tree_node *child)
+{
+ if (parent->child_count == parent->child_capacity) {
+ size_t new_capacity =
+ parent->child_capacity ? parent->child_capacity * 2 : 4;
+
+ parent->children = xreallocarray(parent->children, new_capacity,
+ sizeof(tree_node *));
+ parent->child_capacity = new_capacity;
+ }
+ parent->children[parent->child_count++] = child;
+ child->parent = parent;
+ return 0;
+}
+void
+update_node_data(tree_node *node, void *data)
+{
+ if (node->data && node->free_fn) {
+ node->free_fn(node->data);
+ }
+ node->data = data;
+}
+
+// Free a node and all its children
+void
+free_tree(tree_node *node)
+{
+ if (!node)
+ return;
+ for (size_t i = 0; i < node->child_count; ++i) {
+ free_tree(node->children[i]);
+ }
+ if (node->free_fn && node->data) {
+ node->free_fn(node->data);
+ }
+ free(node->children);
+ free(node);
+}
+
+void
+print_tree(tree_node *node, int depth, void (*print_data)(void *))
+{
+ for (int i = 0; i < depth; i++)
+ printf(" ");
+ if (print_data)
+ print_data(node->data);
+ else
+ printf("(no data)\n");
+
+ for (size_t i = 0; i < node->child_count; ++i) {
+ print_tree(node->children[i], depth + 1, print_data);
+ }
+}
+
+bool
+detach_tree_node(tree_node *node)
+{
+ if (!node || !node->parent)
+ return false;
+
+ tree_node *parent = node->parent;
+
+ // Find index of this node in parent->children
+ size_t index = (size_t)-1;
+ for (size_t i = 0; i < parent->child_count; ++i) {
+ if (parent->children[i] == node) {
+ index = i;
+ break;
+ }
+ }
+ if (index == (size_t)-1)
+ return false; // not found, shouldn't happen
+
+ // Shift remaining children to fill the gap
+ for (size_t i = index + 1; i < parent->child_count; ++i) {
+ parent->children[i - 1] = parent->children[i];
+ }
+ parent->child_count--;
+
+ node->parent = NULL;
+ return true;
+}
diff --git a/tree.h b/tree.h
@@ -0,0 +1,38 @@
+#ifndef tree_h
+#define tree_h
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+typedef struct tree_node {
+ void *data;
+
+ struct tree_node *parent;
+ struct tree_node **children;
+ size_t child_count;
+ size_t child_capacity;
+
+ void (*free_fn)(void *);
+} tree_node;
+
+tree_node *
+create_tree_node(void *data, void (*free_fn)(void *));
+
+void
+update_node_data(tree_node *node, void *data);
+
+// detach from parent
+bool
+detach_tree_node(tree_node *node);
+
+size_t
+add_child(tree_node *parent, tree_node *child);
+
+void
+free_tree(tree_node *node);
+
+void
+print_tree(tree_node *node, int depth, void (*print_data)(void *));
+
+#endif
+