agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 702aba8c32d11f478b35e6996138ed8bd8b7811b
parent 985fbcacfe752019b8da39c7660f142c18237466
Author: Marc Coquand <marc@coquand.email>
Date: Thu, 24 Jul 2025 12:19:46 +0200
Refactor 1
Diffstat:
12 files changed, 1215 insertions(+), 1093 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
@@ -0,0 +1,9 @@
+# Contributing
+
+## Memory model
+
+- `memory_region` is for operation-scoped memory (get,read,write)
+- `agenda_entry` heap allocated and must be freed manually
+- Tree nodes free automatically on `free_node`
+
+Avoid mixing `memory_region` pointers with global state.
diff --git a/agenda_entry.c b/agenda_entry.c
@@ -1,9 +1,5 @@
#include "agenda_entry.h"
-#include "hashmap.h"
-#include "ical_extra.h"
#include "mregion.h"
-#include "path.h"
-#include "tree.h"
#include "util.h"
#include <dirent.h>
#include <libical/ical.h>
@@ -20,18 +16,6 @@
#include <uuid/uuid.h>
#include <wordexp.h>
-char VDIR[256];
-
-// Most memory is managed by memory_region, two exceptions are the
-// global states fuse_root and entries_original_ics, which manages its own
-// resources.
-
-// A structure of the current root
-struct tree_node *fuse_root = NULL;
-// ics entries, keys are the filename as stored in ICS_DIR
-// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
-struct hashmap *entries_vdir = NULL;
-
struct agenda_entry *
copy_agenda_entry(const struct agenda_entry *src)
{
@@ -61,938 +45,16 @@ free_agenda_entry(struct agenda_entry *entry)
return;
}
-struct tree_node *
-get_node_by_uuid(memory_region *mreg, const char *target_uuid)
-{
- char *filename_vdir = NULL;
-
- // Filenames are uid.ics, so we levarage that
- size_t wRes = rasprintf(mreg, &filename_vdir, "%s.ics", target_uuid);
- assert(wRes != -1);
-
- LOG("Looking for UID: '%s'", target_uuid);
- LOG("Full filename: '%s'", filename_vdir);
-
- struct tree_node *node = hashmap_get(entries_vdir, filename_vdir);
- LOG("Found it: %b", node != NULL);
-
- return node;
-}
-
-char *
-get_node_filename(memory_region *mreg, const struct tree_node *node)
-{
- struct agenda_entry *entry = node->data;
- if (entry == NULL)
- return "";
- return rstrdup(mreg, entry->filename);
-}
-
-struct tree_node *
-get_node_by_path(memory_region *mreg, const char *path)
-{
- if (strcmp(path, "/") == 0) {
- LOG("Is ROOT %s", path);
- return fuse_root;
- }
-
- char *segments[255];
- size_t count = split_path(mreg, path, segments);
-
- LOG("Segments are defined");
-
- struct tree_node *current = fuse_root;
-
- for (size_t i = 0; i < count && current; ++i) {
- const char *segment = segments[i];
- struct tree_node *next = NULL;
- LOG("Segment: %s", segment);
-
- for (size_t j = 0; j < current->child_count; ++j) {
-
- const char *child_name =
- get_node_filename(mreg, current->children[j]);
- if (strcmp(child_name, segment) == 0) {
-
- next = current->children[j];
- break;
- }
- }
- current = next;
- }
- LOG("Done");
- return current;
-}
-
-bool
-node_is_directory(memory_region *mreg, const struct tree_node *node,
- icalcomponent *node_ics)
-{
- if (is_root_node(node) || node_has_children(node)) {
- return true;
- }
- else {
- // When user creates a directory, a directory component is
- // stored in the ics file. So even if a node has no children
- // it can still be that it should be shown as a directory.
- return is_directory_component(node_ics);
- }
-}
-
-bool
-is_root_node(const struct tree_node *node)
-{
- return !node->data;
-}
-
-struct agenda_entry *
-get_entry(const struct tree_node *node)
-{
- assert(node->data);
- return node->data;
-}
-
-char *
-get_vdir_filepath(memory_region *mreg, const struct tree_node *node)
-{
- struct agenda_entry *entry = get_entry(node);
- return append_path(mreg, VDIR, entry->filename_vdir);
-}
-
-size_t
-write_ical_file(memory_region *mreg, const struct tree_node *node,
- icalcomponent *ic)
-{
-
- // Always set last modified to the time we write
- icalproperty *prop =
- icalcomponent_get_first_property(ic, ICAL_LASTMODIFIED_PROPERTY);
- if (!prop) {
- prop = icalproperty_new_lastmodified(get_ical_now());
- icalcomponent_add_property(ic, prop);
- }
- else {
- icalproperty_set_lastmodified(prop, get_ical_now());
- }
-
- icalcomponent_set_status(ic, ICAL_STATUS_FINAL);
-
- const char *descr_prop = icalcomponent_get_description(ic);
- if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
- icalproperty *ical_descr_prop =
- icalcomponent_get_first_property(
- icalcomponent_get_inner(ic), ICAL_DESCRIPTION_PROPERTY);
- icalcomponent_remove_property(icalcomponent_get_inner(ic),
- ical_descr_prop);
- }
-
- char *ical_str = ricalcomponent_as_ical_string_r(mreg, ic);
-
- int res = write_to_file(get_vdir_filepath(mreg, node), ical_str);
-
- return res;
-}
-
-// Owner: ctx
-icalcomponent *
-get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n)
-{
- if (is_root_node(n)) {
- return NULL;
- }
-
- char *orig_path = get_vdir_filepath(mreg, n);
- // TODO: Cache intermediate results within context
- icalcomponent *ic = parse_ics_file(mreg, orig_path);
- return ic;
-}
-
-int
-write_parent_child_components(memory_region *mreg,
- const struct tree_node *parent,
- icalcomponent *iparent,
- const struct tree_node *child,
- icalcomponent *ichild)
-{
- LOG("Adding parent and child relation");
- set_parent_child_relationship_to_component(iparent, ichild);
- return write_ical_file(mreg, child, ichild);
-}
-
-// You also will need to update the ics components
-int
-move_node(struct tree_node *new_parent, struct tree_node *child)
-{
- detach_tree_node(child);
- return add_child(new_parent, child);
-}
-
-int
-update_node_filename(struct tree_node *node, const char *filename)
-{
- struct agenda_entry *entry = node->data;
- free(entry->filename);
- entry->filename = xstrdup(filename);
- return 0;
-}
-
-// Writes ics component summary to the filename of the tree_node
-int
-update_summary(memory_region *mreg, icalcomponent *ics,
- const struct tree_node *node)
-{
- struct agenda_entry *e = get_entry(node);
- icalcomponent_set_summary(ics, e->filename);
- return write_ical_file(mreg, node, ics);
-}
-
-// Agenda entries are NOT stored in memory_region, and need to be freed
-// manually
-struct agenda_entry *
-parse_ics_to_agenda_entry(memory_region *mreg, const char *filepath)
-{
- icalcomponent *component = parse_ics_file(mreg, filepath);
- if (component == NULL) {
- LOG("No component");
- return NULL;
- }
- icalcomponent *journal =
- icalcomponent_get_first_real_component(component);
-
- if (journal == NULL ||
- icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
- LOG("Not journal component");
- return NULL;
- }
- if (icalcomponent_get_summary(component) == NULL) {
- LOG("No summary found");
- return NULL;
- }
- struct agenda_entry *e = xmalloc(sizeof(struct agenda_entry));
- region_register(mreg, e, (void *)free_agenda_entry);
-
- e->filename = xstrdup(icalcomponent_get_summary(component));
-
- e->filename_vdir = xstrdup(get_filename(filepath));
-
- return e;
-}
-
-// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
-// to take ownership!
struct agenda_entry *
-load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
-{
- path *filepath = append_path(mreg, VDIR, filename);
- LOG("Filepath is %s", filepath);
-
- struct stat fileStat;
- if (stat(filepath, &fileStat) == -1) {
- LOG("Can not stat %s. skipping", filepath);
- return NULL;
- }
-
- struct agenda_entry *new_entry =
- parse_ics_to_agenda_entry(mreg, filepath);
- if (!new_entry) {
- LOG("Could not parse entry");
- return NULL;
- }
-
- // TODO: Load these from the original file dynamically instead.
- new_entry->uid = fileStat.st_uid;
- new_entry->gid = fileStat.st_gid;
- new_entry->ino = fileStat.st_ino;
-
- LOG("Parsed %s to file %s", new_entry->filename_vdir,
- new_entry->filename);
- return new_entry;
-}
-
-void
-load_root_node_tree()
-{
- entries_vdir = hashmap_new(NULL);
- fuse_root = create_tree_node(NULL, NULL);
- memory_region *mreg = create_region();
- LOG("Loading journal entries from: %s\n", VDIR);
-
- DIR *dir = opendir(VDIR);
- if (!dir) {
- perror("opendir");
- return;
- }
-
- struct dirent *entry;
- while ((entry = readdir(dir))) {
- if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
- LOG("Loading %s", entry->d_name);
- struct agenda_entry *new_entry =
- load_agenda_entry_from_ics_file(mreg,
- entry->d_name);
-
- struct agenda_entry *owned_entry =
- copy_agenda_entry(new_entry);
-
- if (!new_entry) {
- LOG("Skipping entry %s", entry->d_name);
- continue;
- }
-
- struct tree_node *new_node = create_tree_node(
- owned_entry, (void *)free_agenda_entry);
-
- hashmap_insert(entries_vdir, new_entry->filename_vdir,
- new_node);
-
- LOG("Inserted filename_vdir: %s, %s",
- owned_entry->filename, owned_entry->filename_vdir);
- }
- }
- closedir(dir);
- LOG("Set up directories according to parent-child");
-
- size_t n_keys = 0;
- char **keys = hashmap_get_keys(entries_vdir, &n_keys);
- for (size_t i = 0; i < n_keys; i++) {
- const char *filename = keys[i];
- struct tree_node *child = hashmap_get(entries_vdir, filename);
-
- LOG("Parsing %s", filename);
- icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
- LOG("Success");
-
- const char *parent_uid = get_parent_uid(ic);
- if (parent_uid) {
- LOG("Has parent");
-
- struct tree_node *parent =
- get_node_by_uuid(mreg, parent_uid);
- if (parent) {
- detach_tree_node(child);
- add_child(parent, child);
- }
- else {
- LOG("COULD NOT FIND PARENT NODE: %s",
- parent_uid);
- }
- }
- else {
- add_child(fuse_root, child);
- }
- }
- LOG("Inserted %zu entries", n_keys);
-
- for (int i = 0; i < n_keys; i++) {
- free(keys[i]);
- }
- free(keys);
- rfree_all(mreg);
-
- LOG("Done");
-}
-
-size_t
-load_agendafs_environment(char *vdir_env)
-{
- wordexp_t expanded;
- if (wordexp(vdir_env, &expanded, 0) != 0 || expanded.we_wordc == 0) {
- fprintf(stderr, "Failed to expand CALDAVFS_ICS_DIR\n");
- wordfree(&expanded);
- return -1;
- }
-
- char *expanded_path = expanded.we_wordv[0];
-
- struct stat s;
- if (stat(expanded_path, &s) == 0 && S_ISDIR(s.st_mode)) {
- strlcpy(VDIR, expanded_path, sizeof(VDIR));
- wordfree(&expanded);
- return 0;
- }
-
- fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
- expanded_path);
- return -1;
-}
-
-static int
-append_category_to_memstream(FILE *memstream, const char *category,
- bool is_first_category)
+create_agenda_entry(memory_region *mreg, const char *filename,
+ const char *filename_vdir)
{
- int res = 0;
- if (!is_first_category) {
- res = fputs(",", memstream);
- assert(res != EOF);
- }
-
- res = fputs(category, memstream);
- assert(res != EOF);
- return 0;
-}
-
-void
-icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
- const char *buf, size_t size, off_t offset)
-{
-
- const char *old_desc = icalcomponent_get_description(ic);
- if (!old_desc)
- old_desc = "";
-
- char *new_desc = rstrins(mreg, old_desc, offset, buf, size);
- icalcomponent_set_description(ic, new_desc);
-}
-
-// Returns comma separated list of categories for a file
-char *
-get_node_categories(memory_region *mreg, const struct tree_node *node)
-{
- icalcomponent *component = get_icalcomponent_from_node(mreg, node);
- if (!component) {
- return NULL;
- }
-
- FILE *memstream = NULL;
- char *result_buffer = NULL;
- region_register(mreg, result_buffer, free);
- size_t buffer_size = 0;
-
- memstream = open_memstream(&result_buffer, &buffer_size);
-
- icalcomponent *inner = icalcomponent_get_inner(component);
-
- icalproperty *catp =
- icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
- bool first_category = true;
- while (catp) {
- const char *category = icalproperty_get_categories(catp);
- append_category_to_memstream(memstream, category,
- first_category);
- catp = icalcomponent_get_next_property(
- inner, ICAL_CATEGORIES_PROPERTY);
-
- first_category = false;
- }
-
- size_t res = fclose(memstream);
- assert(res != EOF);
-
- return result_buffer;
-}
-
-int
-delete_node_categories(memory_region *mreg, const struct tree_node *node)
-{
- icalcomponent *component = get_icalcomponent_from_node(mreg, node);
- if (!component) {
- return 0;
- }
-
- icalcomponent *inner = icalcomponent_get_inner(component);
-
- icalproperty *catp =
- icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
- // Delete old categories
- while (catp) {
- icalcomponent_remove_property(inner, catp);
-
- catp = icalcomponent_get_next_property(
- inner, ICAL_CATEGORIES_PROPERTY);
- }
-
- return write_ical_file(mreg, node, component);
-}
-
-int
-set_node_categories(memory_region *mreg, const struct tree_node *node,
- const char *new_categories, size_t s)
-{
- icalcomponent *component = get_icalcomponent_from_node(mreg, node);
- if (!component) {
- return 0;
- }
-
- icalcomponent *inner = icalcomponent_get_inner(component);
-
- icalproperty *catp =
- icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
-
- // Delete old categories
- while (catp) {
- icalcomponent_remove_property(inner, catp);
-
- catp = icalcomponent_get_next_property(
- inner, ICAL_CATEGORIES_PROPERTY);
- }
-
- if (s == 0) {
- return write_ical_file(mreg, node, component);
- }
-
- char *null_terminated_categories = rstrndup(mreg, new_categories, s);
-
- icalproperty *new_cat_prop =
- icalproperty_new_categories(null_terminated_categories);
- icalcomponent_add_property(inner, new_cat_prop);
-
- return write_ical_file(mreg, node, component);
-}
-
-const char *
-get_node_class(memory_region *mreg, const struct tree_node *node)
-{
- icalcomponent *component = get_icalcomponent_from_node(mreg, node);
- if (!component) {
- return 0;
- }
-
- icalcomponent *inner = icalcomponent_get_inner(component);
-
- const icalproperty *classp =
- icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
- const enum icalproperty_class classv = icalproperty_get_class(classp);
-
- if (classp) {
- return format_ical_class(classv);
- }
-
- return "";
-}
-
-int
-delete_node_class(memory_region *mreg, const struct tree_node *node)
-{
- icalcomponent *component = get_icalcomponent_from_node(mreg, node);
- if (!component) {
- return 0;
- }
- icalcomponent *inner = icalcomponent_get_inner(component);
- icalproperty *prop =
- icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
- icalcomponent_remove_property(inner, prop);
-
- return write_ical_file(mreg, node, component);
-}
-
-int
-set_node_class(memory_region *mreg, const struct tree_node *node,
- const icalproperty_class new_class)
-{
- icalcomponent *component = get_icalcomponent_from_node(mreg, node);
- if (!component) {
- return 0;
- }
-
- icalcomponent *inner = icalcomponent_get_inner(component);
-
- icalproperty *classp =
- icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
- icalcomponent_remove_property(inner, classp);
-
- icalproperty *new_class_prop = icalproperty_new_class(new_class);
- icalcomponent_add_property(inner, new_class_prop);
-
- return write_ical_file(mreg, node, component);
-}
-
-uid_t
-get_node_uid(const struct tree_node *node)
-{
- if (is_root_node(node)) {
- return getuid();
- }
- return get_entry(node)->uid;
-}
-
-gid_t
-get_node_gid(const struct tree_node *node)
-{
- if (is_root_node(node)) {
- return getgid();
- }
- return get_entry(node)->gid;
-}
-ino_t
-get_node_ino(const struct tree_node *node)
-{
- if (is_root_node(node)) {
- return 0;
- }
- return get_entry(node)->ino;
-}
-
-int
-create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
-{
- // TODO: We are mixing some responsibilities here.
- // Any function that operates on the global state should not
- // have memory_region as a parameter, as that makes it easier
- // to mix up the contexts.
- const char *entry_prefix = "/";
- if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
- return -EINVAL;
- }
- const char *new_basename = get_filename(fuse_path);
-
- struct agenda_entry *new_entry = xmalloc(sizeof(struct agenda_entry));
- icalcomponent *new_component =
- create_vjournal_entry(mreg, new_basename);
-
- new_entry->filename = xstrdup(new_basename);
- if (asprintf(&new_entry->filename_vdir, "%s.ics",
- icalcomponent_get_uid(new_component)) == -1) {
- LOG("Failed to create journal entry");
- free_agenda_entry(new_entry);
- return -ENOMEM;
- }
- LOG("Inserting vjournal directory");
-
- struct tree_node *new_node =
- create_tree_node(new_entry, (void *)free_agenda_entry);
-
- char *parent_path = get_parent_path(mreg, fuse_path);
- LOG("Parent path is %s", parent_path);
- struct tree_node *parent = get_node_by_path(mreg, parent_path);
- icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
-
- if (!parent) {
- LOG("Parent does not exist");
- return -ENOENT;
- }
- if (!is_root_node(parent) ||
- (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
- LOG("Parent is not directory ");
- return -ENOTDIR;
- }
-
- if (parent_ics) {
- write_parent_child_components(mreg, parent, parent_ics,
- new_node, new_component);
-
- add_child(parent, new_node);
- }
-
- hashmap_insert(entries_vdir, new_entry->filename_vdir, new_node);
-
- if (write_ical_file(mreg, new_node, new_component) != 0) {
- LOG("Write to entry failed");
- hashmap_remove(entries_vdir, new_entry->filename_vdir);
-
- free_tree(new_node);
- return -EIO;
- }
-
- LOG("FILE CREATED");
- return 0;
-}
-
-int
-create_directory_from_fuse_path(memory_region *mreg, 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 agenda_entry *new_entry =
- xcalloc(1, sizeof(struct agenda_entry));
- icalcomponent *new_component =
- create_vjournal_directory(mreg, new_basename);
-
- new_entry->filename = xstrdup(new_basename);
- if (asprintf(&new_entry->filename_vdir, "%s.ics",
- icalcomponent_get_uid(new_component)) == -1) {
- LOG("Failed to create journal entry");
- free_agenda_entry(new_entry);
- return -ENOMEM;
- }
- LOG("Inserting vjournal directory");
-
- struct tree_node *new_node =
- create_tree_node(new_entry, (void *)free_agenda_entry);
-
- char *parent_path = get_parent_path(mreg, fuse_path);
- LOG("Parent path is %s", parent_path);
- struct tree_node *parent = get_node_by_path(mreg, parent_path);
- icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
-
- if (!parent) {
- LOG("Parent does not exist");
- return -ENOENT;
- }
- if (!is_root_node(parent) ||
- (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
- LOG("Parent is not directory ");
- return -ENOTDIR;
- }
-
- if (parent_ics) {
- write_parent_child_components(mreg, parent, parent_ics,
- new_node, new_component);
-
- add_child(parent, new_node);
- }
-
- hashmap_insert(entries_vdir, new_entry->filename_vdir, new_node);
-
- if (write_ical_file(mreg, new_node, new_component) != 0) {
- LOG("Write to entry failed");
- hashmap_remove(entries_vdir, new_entry->filename_vdir);
-
- free_tree(new_node);
- return -EIO;
- }
-
- LOG("DIRECTORY CREATED");
- return 0;
-}
-
-void
-update_or_create_fuse_entry_from_vdir(memory_region *mreg,
- const char *filepath_vdir)
-{
- const char *filename_vdir = get_filename(filepath_vdir);
- LOG("Filename original is %s", filename_vdir);
-
- struct agenda_entry *updated_entry =
- load_agenda_entry_from_ics_file(mreg, filename_vdir);
-
- struct agenda_entry *updated_entry_owned =
- copy_agenda_entry(updated_entry);
-
- struct tree_node *new_or_updated_node =
- hashmap_get(entries_vdir, filename_vdir);
-
- if (new_or_updated_node) {
- LOG("Updating existing entry");
- update_node_data(new_or_updated_node, updated_entry_owned);
- }
- else {
- LOG("Creating new entry");
- new_or_updated_node = create_tree_node(
- updated_entry_owned, (void *)free_agenda_entry);
- hashmap_insert(entries_vdir, updated_entry_owned->filename_vdir,
- new_or_updated_node);
- }
-
- icalcomponent *entry_ics =
- get_icalcomponent_from_node(mreg, new_or_updated_node);
-
- const char *new_parent_uid = get_parent_uid(entry_ics);
-
- if (new_parent_uid) {
- LOG("Has parent");
- struct tree_node *new_parent =
- get_node_by_uuid(mreg, new_parent_uid);
- move_node(new_parent, new_or_updated_node);
- }
- else {
- LOG("Does not have parent, adding to root");
- detach_tree_node(new_or_updated_node);
- add_child(fuse_root, new_or_updated_node);
- }
-
- LOG("Tree updated");
-}
-
-int
-delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
-{
- int res = 0;
- struct tree_node *node = get_node_by_path(mreg, filepath);
- if (!node) {
- return -EIO;
- }
-
- icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
- if (!node_is_directory(mreg, node, node_ics)) {
- return -ENOTDIR;
- }
- if (node->child_count > 0) {
- return -ENOTEMPTY;
- }
-
- char *filepath_original = get_vdir_filepath(mreg, node);
- if (!filepath_original) {
- LOG("Entry not found");
- return -EIO;
- }
- LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
- res = remove(filepath_original);
- if (res != 0) {
- LOG("Failed to delete file");
- return -res;
- }
-
- hashmap_remove(entries_vdir, filepath_original);
-
- detach_tree_node(node);
- free_tree(node);
- return res;
-}
-
-int
-do_agenda_rename(memory_region *mreg, const char *old, const char *new)
-{
- char *new_copy = rstrdup(mreg, new);
- const char *new_filename = get_filename(new_copy);
-
- const char *old_filename = get_filename(old);
- if (!old_filename)
- return -EINVAL;
-
- struct tree_node *new_parent_node =
- get_node_by_path(mreg, get_parent_path(mreg, new));
-
- if (new_parent_node == NULL) {
- return -EINVAL;
- }
-
- char *new_summary = rstrdup(mreg, new_filename);
-
- // Remove everything after extension
- // TODO: Store the extension as a separate field in the ics file
- char *dot = strrchr(new_summary, '.');
- if (dot) {
- *dot = '\0';
- }
-
- LOG("Summary is now %s", new_summary);
- struct tree_node *child_node = get_node_by_path(mreg, old);
- assert(child_node);
- icalcomponent *child_ics =
- get_icalcomponent_from_node(mreg, child_node);
- assert(child_ics);
-
- icalcomponent *new_parent_ics =
- get_icalcomponent_from_node(mreg, new_parent_node);
-
- struct tree_node *old_parent_node = child_node->parent;
- icalcomponent *old_parent_ics =
- get_icalcomponent_from_node(mreg, old_parent_node);
-
- update_node_filename(child_node, new_filename);
- update_summary(mreg, child_ics, child_node);
-
- if (old_parent_node && old_parent_ics) {
- remove_parent_child_relationship_from_component(old_parent_ics,
- child_ics);
- }
-
- detach_tree_node(child_node);
-
- if (new_parent_ics) {
- move_node(new_parent_node, child_node);
- write_parent_child_components(mreg, new_parent_node,
- new_parent_ics, child_node,
- child_ics);
- }
- else {
- add_child(fuse_root, child_node);
- }
- return 0;
-}
-
-int
-delete_from_fuse_path(memory_region *mreg, const char *filepath)
-{
- int res = 0;
- struct tree_node *node = get_node_by_path(mreg, filepath);
- if (!node) {
- return -ENOENT;
- }
-
- icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
- if (node_is_directory(mreg, node, node_ics)) {
- return -EISDIR;
- }
- char *filepath_original = get_vdir_filepath(mreg, node);
-
- LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
- res = remove(filepath_original);
- if (res == -1) {
- return -EIO;
- }
-
- hashmap_remove(entries_vdir, filepath_original);
- detach_tree_node(node);
- free_tree(node);
- return res;
-}
-
-char *
-get_node_path(memory_region *mreg, struct tree_node *node)
-{
- if (!node->parent) {
- return "/";
- }
-
- struct tree_node *curr = node;
- char *result = NULL;
- size_t result_len = 0;
-
- FILE *stream = open_memstream(&result, &result_len);
- assert(stream);
-
- do {
- if (!is_root_node(curr)) {
- char *filename = get_node_filename(mreg, curr);
- fprintf(stream, "/%s", filename);
- }
-
- curr = curr->parent;
-
- } while (curr->parent);
-
- fclose(stream);
- if (result) {
- region_register(mreg, result, free);
- }
-
- return result;
-}
-
-char *
-get_fuse_path_from_vdir(memory_region *mreg, const char *filepath)
-{
- struct tree_node *node =
- hashmap_get(entries_vdir, get_filename(filepath));
-
- if (node) {
- return get_node_path(mreg, node);
- }
- return NULL;
-}
-
-int
-delete_from_vdir_path(memory_region *mreg, const char *filepath)
-{
- // Strip path, validate just the new basename
- const char *keyname = get_filename(filepath);
- struct agenda_entry *entry = hashmap_get(entries_vdir, keyname);
- if (!entry) {
- return -EIO;
- }
-
- // TODO: Fix so it finds by actual UUID, not filepath
- struct tree_node *node = get_node_by_uuid(mreg, 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_vdir, keyname);
- return 0;
+ struct agenda_entry *copy = xmalloc(sizeof(struct agenda_entry));
+ copy->filename = xstrdup(filename);
+ copy->filename_vdir = xstrdup(filename_vdir);
+ // copy->gid = src->gid;
+ // copy->uid = src->uid;
+ // copy->ino = src->ino;
+ region_register(mreg, copy, (void *)free_agenda_entry);
+ return copy;
}
diff --git a/agenda_entry.h b/agenda_entry.h
@@ -33,153 +33,14 @@ struct agenda_entry {
ino_t ino;
};
-extern char VDIR[256];
-
-extern const char *IS_DIRECTORY_PROPERTY;
-
-// A structure of the current root
-extern struct tree_node *fuse_root;
-// ics entries, keys are the filename as stored in ICS_DIR
-// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
-extern struct hashmap *entries_vdir;
-
struct agenda_entry *
copy_agenda_entry(const struct agenda_entry *src);
void
free_agenda_entry(struct agenda_entry *entry);
-struct tree_node *
-get_node_by_uuid(memory_region *mreg, const char *target_uuid);
-
-char *
-get_node_filename(memory_region *mreg, const struct tree_node *node);
-
-struct tree_node *
-get_node_by_path(memory_region *mreg, const char *path);
-
-bool
-node_is_directory(memory_region *mreg, const struct tree_node *node,
- icalcomponent *node_ics);
-
-bool
-is_root_node(const struct tree_node *node);
-
-struct agenda_entry *
-get_entry(const struct tree_node *node);
-
-char *
-get_vdir_filepath(memory_region *mreg, const struct tree_node *node);
-
-// Owner: ctx
-icalcomponent *
-parse_ics_file(memory_region *mreg, const char *filename);
-
-icaltimetype
-get_ical_now();
-
-size_t
-write_ical_file(memory_region *mreg, const struct tree_node *node,
- icalcomponent *ic);
-
-// Owner: ctx
-icalcomponent *
-get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n);
-
-int
-write_parent_child_components(memory_region *mreg,
- const struct tree_node *parent,
- icalcomponent *iparent,
- const struct tree_node *child,
- icalcomponent *ichild);
-
-// You also will need to update the ics components
-int
-move_node(struct tree_node *new_parent, struct tree_node *child);
-
-int
-update_node_filename(struct tree_node *node, const char *filename);
-
-// Writes ics component summary to the filename of the tree_node
-int
-update_summary(memory_region *mreg, icalcomponent *ics,
- const struct tree_node *node);
-
struct agenda_entry *
-parse_ics_to_agenda_entry(memory_region *mreg, const char *filename);
-
-// Will be cleaned up by mreg
-struct agenda_entry *
-load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename);
-
-void
-load_root_node_tree();
-
-size_t
-load_agendafs_environment(char *);
-
-// Returns comma separated list of categories for a file
-char *
-get_node_categories(memory_region *mreg, const struct tree_node *node);
-
-int
-delete_node_categories(memory_region *mreg, const struct tree_node *node);
-
-int
-set_node_categories(memory_region *mreg, const struct tree_node *node,
- const char *new_categories, size_t s);
-
-const char *
-format_ical_class(const enum icalproperty_class iclass);
-
-const icalproperty_class
-parse_ical_class(const char *input);
-
-const char *
-get_node_class(memory_region *mreg, const struct tree_node *node);
-
-int
-delete_node_class(memory_region *mreg, const struct tree_node *node);
-
-int
-set_node_class(memory_region *mreg, const struct tree_node *node,
- const icalproperty_class new_class);
-
-char *
-create_new_unique_ics_uid(memory_region *mreg);
-ino_t
-get_node_ino(const struct tree_node *node);
-char *
-get_fuse_path_from_vdir(memory_region *mreg, const char *filepath);
-
-uid_t
-get_node_uid(const struct tree_node *node);
-
-gid_t
-get_node_gid(const struct tree_node *node);
-
-int
-create_entry_from_fuse(memory_region *mreg, const char *fuse_path);
-
-int
-create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path);
-
-void
-update_or_create_fuse_entry_from_vdir(memory_region *, const char *);
-
-int
-delete_dir_from_fuse_path(memory_region *mreg, const char *filepath);
-
-int
-do_agenda_rename(memory_region *, const char *, const char *);
-
-int
-delete_from_fuse_path(memory_region *mreg, const char *filepath);
-
-void
-icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
- const char *buf, size_t size, off_t offset);
-int
-delete_from_vdir_path(memory_region *mreg, const char *filepath);
+create_agenda_entry(memory_region *mreg, const char *filename,
+ const char *filename_vdir);
#endif // agenda_entry_h_INCLUDED
diff --git a/fuse_node.c b/fuse_node.c
@@ -0,0 +1,907 @@
+#include "agenda_entry.h"
+#include "fuse_node_store.h"
+#include "hashmap.h"
+#include "ical_extra.h"
+#include "mregion.h"
+#include "path.h"
+#include "tree.h"
+#include "util.h"
+#include <dirent.h>
+#include <libical/ical.h>
+#include <pthread.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <wordexp.h>
+
+struct tree_node *
+get_node_by_uuid(memory_region *mreg, const char *target_uuid)
+{
+ char *filename_vdir = NULL;
+
+ // Filenames are uid.ics, so we levarage that
+ size_t wRes = rasprintf(mreg, &filename_vdir, "%s.ics", target_uuid);
+ assert(wRes != -1);
+
+ LOG("Looking for UID: '%s'", target_uuid);
+ LOG("Full filename: '%s'", filename_vdir);
+
+ struct tree_node *node = get_fuse_node_from_vdir_name(filename_vdir);
+ LOG("Found it: %b", node != NULL);
+
+ return node;
+}
+
+char *
+get_node_filename(memory_region *mreg, const struct tree_node *node)
+{
+ struct agenda_entry *entry = node->data;
+ if (entry == NULL)
+ return "";
+ return rstrdup(mreg, entry->filename);
+}
+
+struct tree_node *
+get_node_by_path(memory_region *mreg, const char *path)
+{
+ if (strcmp(path, "/") == 0) {
+ LOG("Is ROOT %s", path);
+ return fuse_root;
+ }
+
+ char *segments[255];
+ size_t count = split_path(mreg, path, segments);
+
+ LOG("Segments are defined");
+
+ struct tree_node *current = fuse_root;
+
+ for (size_t i = 0; i < count && current; ++i) {
+ const char *segment = segments[i];
+ struct tree_node *next = NULL;
+ LOG("Segment: %s", segment);
+
+ for (size_t j = 0; j < current->child_count; ++j) {
+
+ const char *child_name =
+ get_node_filename(mreg, current->children[j]);
+ if (strcmp(child_name, segment) == 0) {
+
+ next = current->children[j];
+ break;
+ }
+ }
+ current = next;
+ }
+ LOG("Done");
+ return current;
+}
+
+bool
+is_root_node(const struct tree_node *node)
+{
+ return !node->data;
+}
+
+bool
+node_is_directory(memory_region *mreg, const struct tree_node *node,
+ icalcomponent *node_ics)
+{
+ if (is_root_node(node) || node_has_children(node)) {
+ return true;
+ }
+ else {
+ // When user creates a directory, a directory component is
+ // stored in the ics file. So even if a node has no children
+ // it can still be that it should be shown as a directory.
+ return is_directory_component(node_ics);
+ }
+}
+
+size_t
+write_ical_file(memory_region *mreg, const struct tree_node *node,
+ icalcomponent *ic)
+{
+
+ // Always set last modified to the time we write
+ icalproperty *prop =
+ icalcomponent_get_first_property(ic, ICAL_LASTMODIFIED_PROPERTY);
+ if (!prop) {
+ prop = icalproperty_new_lastmodified(get_ical_now());
+ icalcomponent_add_property(ic, prop);
+ }
+ else {
+ icalproperty_set_lastmodified(prop, get_ical_now());
+ }
+
+ icalcomponent_set_status(ic, ICAL_STATUS_FINAL);
+
+ const char *descr_prop = icalcomponent_get_description(ic);
+ if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
+ icalproperty *ical_descr_prop =
+ icalcomponent_get_first_property(
+ icalcomponent_get_inner(ic), ICAL_DESCRIPTION_PROPERTY);
+ icalcomponent_remove_property(icalcomponent_get_inner(ic),
+ ical_descr_prop);
+ }
+
+ char *ical_str = ricalcomponent_as_ical_string_r(mreg, ic);
+
+ int res = write_to_file(get_vdir_filepath(mreg, node), ical_str);
+
+ return res;
+}
+
+// Owner: ctx
+icalcomponent *
+get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n)
+{
+ if (is_root_node(n)) {
+ return NULL;
+ }
+
+ char *orig_path = get_vdir_filepath(mreg, n);
+ // TODO: Cache intermediate results within context
+ icalcomponent *ic = parse_ics_file(mreg, orig_path);
+ return ic;
+}
+
+int
+write_parent_child_components(memory_region *mreg,
+ const struct tree_node *parent,
+ icalcomponent *iparent,
+ const struct tree_node *child,
+ icalcomponent *ichild)
+{
+ LOG("Adding parent and child relation");
+ set_parent_child_relationship_to_component(iparent, ichild);
+ return write_ical_file(mreg, child, ichild);
+}
+
+// Writes ics component summary to the filename of the tree_node
+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);
+ return write_ical_file(mreg, node, ics);
+}
+
+struct agenda_entry *
+parse_ics_to_agenda_entry(memory_region *mreg, const char *vdir_filepath)
+{
+ icalcomponent *component = parse_ics_file(mreg, vdir_filepath);
+ if (component == NULL) {
+ LOG("No component");
+ return NULL;
+ }
+ icalcomponent *journal =
+ icalcomponent_get_first_real_component(component);
+
+ if (journal == NULL ||
+ icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
+ LOG("Not journal component");
+ return NULL;
+ }
+ if (icalcomponent_get_summary(component) == NULL) {
+ LOG("No summary found");
+ return NULL;
+ }
+ struct agenda_entry *e = xmalloc(sizeof(struct agenda_entry));
+ e->filename = xstrdup(icalcomponent_get_summary(component));
+ e->filename_vdir = xstrdup(get_filename(vdir_filepath));
+
+ region_register(mreg, e, (void *)free_agenda_entry);
+
+ return e;
+}
+
+// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
+// to take ownership!
+struct agenda_entry *
+load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename)
+{
+ path *filepath = append_path(mreg, VDIR, filename);
+ LOG("Filepath is %s", filepath);
+
+ struct stat fileStat;
+ if (stat(filepath, &fileStat) == -1) {
+ LOG("Can not stat %s. skipping", filepath);
+ return NULL;
+ }
+
+ struct agenda_entry *new_entry =
+ parse_ics_to_agenda_entry(mreg, filepath);
+ if (!new_entry) {
+ LOG("Could not parse entry");
+ return NULL;
+ }
+
+ // TODO: Load these from the original file dynamically instead.
+ new_entry->uid = fileStat.st_uid;
+ new_entry->gid = fileStat.st_gid;
+ new_entry->ino = fileStat.st_ino;
+
+ LOG("Parsed %s to file %s", new_entry->filename_vdir,
+ new_entry->filename);
+ return new_entry;
+}
+
+void
+load_root_node_tree()
+{
+ entries_vdir = hashmap_new(NULL);
+ fuse_root = create_tree_node(NULL, NULL);
+ memory_region *mreg = create_region();
+ LOG("Loading journal entries from: %s\n", VDIR);
+
+ DIR *dir = opendir(VDIR);
+ if (!dir) {
+ perror("opendir");
+ return;
+ }
+
+ struct dirent *entry;
+ while ((entry = readdir(dir))) {
+ if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
+ LOG("Loading %s", entry->d_name);
+ struct agenda_entry *new_entry =
+ load_agenda_entry_from_ics_file(mreg,
+ entry->d_name);
+
+ create_fuse_node(new_entry);
+
+ LOG("Inserted filename_vdir: %s, %s",
+ new_entry->filename, new_entry->filename_vdir);
+ }
+ }
+ closedir(dir);
+ LOG("Set up directories according to parent-child");
+
+ size_t n_keys = 0;
+ char **keys = hashmap_get_keys(entries_vdir, &n_keys);
+ for (size_t i = 0; i < n_keys; i++) {
+ const char *vdirname = keys[i];
+ struct tree_node *child =
+ get_fuse_node_from_vdir_name(vdirname);
+
+ LOG("Parsing %s", filename);
+ icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
+ LOG("Success");
+
+ const char *parent_uid = get_parent_uid(ic);
+ if (parent_uid) {
+ LOG("Has parent");
+
+ struct tree_node *parent =
+ get_node_by_uuid(mreg, parent_uid);
+ if (parent) {
+ move_node(parent, child);
+ }
+ else {
+ LOG("COULD NOT FIND PARENT NODE: %s",
+ parent_uid);
+ }
+ }
+ else {
+ add_child(fuse_root, child);
+ }
+ }
+ LOG("Inserted %zu entries", n_keys);
+
+ hashmap_free_keys(keys, n_keys);
+ rfree_all(mreg);
+
+ LOG("Done");
+}
+
+size_t
+load_agendafs_environment(char *vdir_env)
+{
+ wordexp_t expanded;
+ if (wordexp(vdir_env, &expanded, 0) != 0 || expanded.we_wordc == 0) {
+ fprintf(stderr, "Failed to expand CALDAVFS_ICS_DIR\n");
+ wordfree(&expanded);
+ return -1;
+ }
+
+ char *expanded_path = expanded.we_wordv[0];
+
+ struct stat s;
+ if (stat(expanded_path, &s) == 0 && S_ISDIR(s.st_mode)) {
+ assert(strlen(expanded_path) < 256);
+ set_vdir(expanded_path);
+ wordfree(&expanded);
+ return 0;
+ }
+
+ fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
+ expanded_path);
+ return -1;
+}
+
+static int
+append_category_to_memstream(FILE *memstream, const char *category,
+ bool is_first_category)
+{
+ int res = 0;
+ if (!is_first_category) {
+ res = fputs(",", memstream);
+ assert(res != EOF);
+ }
+
+ res = fputs(category, memstream);
+ assert(res != EOF);
+ return 0;
+}
+
+// Returns comma separated list of categories for a file
+char *
+get_node_categories(memory_region *mreg, const struct tree_node *node)
+{
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return NULL;
+ }
+
+ FILE *memstream = NULL;
+ char *result_buffer = NULL;
+ region_register(mreg, result_buffer, free);
+ size_t buffer_size = 0;
+
+ memstream = open_memstream(&result_buffer, &buffer_size);
+
+ icalcomponent *inner = icalcomponent_get_inner(component);
+
+ icalproperty *catp =
+ icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+ bool first_category = true;
+ while (catp) {
+ const char *category = icalproperty_get_categories(catp);
+ append_category_to_memstream(memstream, category,
+ first_category);
+ catp = icalcomponent_get_next_property(
+ inner, ICAL_CATEGORIES_PROPERTY);
+
+ first_category = false;
+ }
+
+ size_t res = fclose(memstream);
+ assert(res != EOF);
+
+ return result_buffer;
+}
+
+int
+delete_node_categories(memory_region *mreg, const struct tree_node *node)
+{
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return 0;
+ }
+
+ icalcomponent *inner = icalcomponent_get_inner(component);
+
+ icalproperty *catp =
+ icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+ // Delete old categories
+ while (catp) {
+ icalcomponent_remove_property(inner, catp);
+
+ catp = icalcomponent_get_next_property(
+ inner, ICAL_CATEGORIES_PROPERTY);
+ }
+
+ return write_ical_file(mreg, node, component);
+}
+
+int
+set_node_categories(memory_region *mreg, const struct tree_node *node,
+ const char *new_categories, size_t s)
+{
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return 0;
+ }
+
+ icalcomponent *inner = icalcomponent_get_inner(component);
+
+ icalproperty *catp =
+ icalcomponent_get_first_property(inner, ICAL_CATEGORIES_PROPERTY);
+
+ // Delete old categories
+ while (catp) {
+ icalcomponent_remove_property(inner, catp);
+
+ catp = icalcomponent_get_next_property(
+ inner, ICAL_CATEGORIES_PROPERTY);
+ }
+
+ if (s == 0) {
+ return write_ical_file(mreg, node, component);
+ }
+
+ char *null_terminated_categories = rstrndup(mreg, new_categories, s);
+
+ icalproperty *new_cat_prop =
+ icalproperty_new_categories(null_terminated_categories);
+ icalcomponent_add_property(inner, new_cat_prop);
+
+ return write_ical_file(mreg, node, component);
+}
+
+const char *
+get_node_class(memory_region *mreg, const struct tree_node *node)
+{
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return 0;
+ }
+
+ icalcomponent *inner = icalcomponent_get_inner(component);
+
+ const icalproperty *classp =
+ icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+ const enum icalproperty_class classv = icalproperty_get_class(classp);
+
+ if (classp) {
+ return format_ical_class(classv);
+ }
+
+ return "";
+}
+
+int
+delete_node_class(memory_region *mreg, const struct tree_node *node)
+{
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return 0;
+ }
+ icalcomponent *inner = icalcomponent_get_inner(component);
+ icalproperty *prop =
+ icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+ icalcomponent_remove_property(inner, prop);
+
+ return write_ical_file(mreg, node, component);
+}
+
+int
+set_node_class(memory_region *mreg, const struct tree_node *node,
+ const icalproperty_class new_class)
+{
+ icalcomponent *component = get_icalcomponent_from_node(mreg, node);
+ if (!component) {
+ return 0;
+ }
+
+ icalcomponent *inner = icalcomponent_get_inner(component);
+
+ icalproperty *classp =
+ icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
+
+ icalcomponent_remove_property(inner, classp);
+
+ icalproperty *new_class_prop = icalproperty_new_class(new_class);
+ icalcomponent_add_property(inner, new_class_prop);
+
+ return write_ical_file(mreg, node, component);
+}
+
+uid_t
+get_node_uid(const struct tree_node *node)
+{
+ if (is_root_node(node)) {
+ return getuid();
+ }
+ return get_entry(node)->uid;
+}
+
+gid_t
+get_node_gid(const struct tree_node *node)
+{
+ if (is_root_node(node)) {
+ return getgid();
+ }
+ return get_entry(node)->gid;
+}
+ino_t
+get_node_ino(const struct tree_node *node)
+{
+ if (is_root_node(node)) {
+ return 0;
+ }
+ return get_entry(node)->ino;
+}
+
+int
+insert_fuse_node_to_path(memory_region *mreg, const char *fuse_path,
+ struct tree_node *child_node, icalcomponent *child_ics)
+{
+ char *parent_path = get_parent_path(mreg, fuse_path);
+ LOG("Parent path is %s", parent_path);
+
+ struct tree_node *parent_node = get_node_by_path(mreg, parent_path);
+ if (!parent_node) {
+ LOG("Parent does not exist");
+ return -ENOENT;
+ }
+
+ icalcomponent *parent_ics =
+ get_icalcomponent_from_node(mreg, parent_node);
+
+ if (!is_root_node(parent_node) ||
+ (parent_ics && !node_is_directory(mreg, parent_node, parent_ics))) {
+ LOG("Parent is not a directory");
+ return -ENOTDIR;
+ }
+
+ if (parent_ics) {
+ write_parent_child_components(mreg, parent_node, parent_ics,
+ child_node, child_ics);
+ }
+
+ add_child(parent_node, child_node);
+ return 0;
+}
+
+int
+create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
+{
+ // TODO: We are mixing some responsibilities here.
+ // Any function that operates on the global state should not
+ // have memory_region as a parameter, as that makes it easier
+ // to mix up the contexts.
+ const char *entry_prefix = "/";
+ if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
+ return -EINVAL;
+ }
+ const char *new_filename = get_filename(fuse_path);
+ icalcomponent *new_component =
+ create_vjournal_entry(mreg, new_filename);
+
+ char *new_filname_vdir = NULL;
+ rasprintf(mreg, &new_filname_vdir, "%s.ics",
+ icalcomponent_get_uid(new_component));
+
+ struct agenda_entry *new_entry =
+ create_agenda_entry(mreg, new_filename, new_filname_vdir);
+
+ LOG("Inserting vjournal directory");
+
+ struct tree_node *new_node = create_fuse_node(new_entry);
+
+ int status =
+ insert_fuse_node_to_path(mreg, fuse_path, new_node, new_component);
+
+ if (status != 0) {
+ return -ENOENT;
+ }
+
+ if (write_ical_file(mreg, new_node, new_component) != 0) {
+ LOG("Write to entry failed");
+ hashmap_remove(entries_vdir, new_entry->filename_vdir);
+
+ free_tree(new_node);
+ return -EIO;
+ }
+
+ LOG("FILE CREATED");
+ return 0;
+}
+
+int
+create_directory_from_fuse_path(memory_region *mreg, 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 agenda_entry *new_entry =
+ xcalloc(1, sizeof(struct agenda_entry));
+ icalcomponent *new_component =
+ create_vjournal_directory(mreg, new_basename);
+
+ new_entry->filename = xstrdup(new_basename);
+ if (asprintf(&new_entry->filename_vdir, "%s.ics",
+ icalcomponent_get_uid(new_component)) == -1) {
+ LOG("Failed to create journal entry");
+ free_agenda_entry(new_entry);
+ return -ENOMEM;
+ }
+ LOG("Inserting vjournal directory");
+
+ struct tree_node *new_node =
+ create_tree_node(new_entry, (void *)free_agenda_entry);
+
+ char *parent_path = get_parent_path(mreg, fuse_path);
+ LOG("Parent path is %s", parent_path);
+ struct tree_node *parent = get_node_by_path(mreg, parent_path);
+ icalcomponent *parent_ics = get_icalcomponent_from_node(mreg, parent);
+
+ if (!parent) {
+ LOG("Parent does not exist");
+ return -ENOENT;
+ }
+ if (!is_root_node(parent) ||
+ (parent_ics && !node_is_directory(mreg, parent, parent_ics))) {
+ LOG("Parent is not directory ");
+ return -ENOTDIR;
+ }
+
+ if (parent_ics) {
+ write_parent_child_components(mreg, parent, parent_ics,
+ new_node, new_component);
+
+ add_child(parent, new_node);
+ }
+
+ hashmap_insert(entries_vdir, new_entry->filename_vdir, new_node);
+
+ if (write_ical_file(mreg, new_node, new_component) != 0) {
+ LOG("Write to entry failed");
+ hashmap_remove(entries_vdir, new_entry->filename_vdir);
+
+ free_tree(new_node);
+ return -EIO;
+ }
+
+ LOG("DIRECTORY CREATED");
+ return 0;
+}
+
+void
+update_or_create_fuse_entry_from_vdir(memory_region *mreg,
+ const char *filepath_vdir)
+{
+ const char *filename_vdir = get_filename(filepath_vdir);
+ LOG("Filename original is %s", filename_vdir);
+
+ struct agenda_entry *updated_entry =
+ load_agenda_entry_from_ics_file(mreg, filename_vdir);
+
+ struct agenda_entry *updated_entry_owned =
+ copy_agenda_entry(updated_entry);
+
+ struct tree_node *new_or_updated_node =
+ hashmap_get(entries_vdir, filename_vdir);
+
+ if (new_or_updated_node) {
+ LOG("Updating existing entry");
+ update_node_data(new_or_updated_node, updated_entry_owned);
+ }
+ else {
+ LOG("Creating new entry");
+ new_or_updated_node = create_tree_node(
+ updated_entry_owned, (void *)free_agenda_entry);
+ hashmap_insert(entries_vdir, updated_entry_owned->filename_vdir,
+ new_or_updated_node);
+ }
+
+ icalcomponent *entry_ics =
+ get_icalcomponent_from_node(mreg, new_or_updated_node);
+
+ const char *new_parent_uid = get_parent_uid(entry_ics);
+
+ if (new_parent_uid) {
+ LOG("Has parent");
+ struct tree_node *new_parent =
+ get_node_by_uuid(mreg, new_parent_uid);
+ move_node(new_parent, new_or_updated_node);
+ }
+ else {
+ LOG("Does not have parent, adding to root");
+ detach_tree_node(new_or_updated_node);
+ add_child(fuse_root, new_or_updated_node);
+ }
+
+ LOG("Tree updated");
+}
+
+int
+delete_dir_from_fuse_path(memory_region *mreg, const char *filepath)
+{
+ int res = 0;
+ struct tree_node *node = get_node_by_path(mreg, filepath);
+ if (!node) {
+ return -EIO;
+ }
+
+ icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+ if (!node_is_directory(mreg, node, node_ics)) {
+ return -ENOTDIR;
+ }
+
+ if (node->child_count > 0) {
+ return -ENOTEMPTY;
+ }
+
+ char *filepath_original = get_vdir_filepath(mreg, node);
+ if (!filepath_original) {
+ LOG("Entry not found");
+ return -EIO;
+ }
+ LOG("Deleting file %s, located at %s", filepath, filepath_original);
+
+ res = remove(filepath_original);
+ if (res != 0) {
+ LOG("Failed to delete file");
+ return -res;
+ }
+
+ delete_fuse_node(node);
+
+ return res;
+}
+
+int
+do_agenda_rename(memory_region *mreg, const char *old, const char *new)
+{
+ char *new_copy = rstrdup(mreg, new);
+ const char *new_filename = get_filename(new_copy);
+
+ const char *old_filename = get_filename(old);
+ if (!old_filename)
+ return -EINVAL;
+
+ struct tree_node *new_parent_node =
+ get_node_by_path(mreg, get_parent_path(mreg, new));
+
+ if (new_parent_node == NULL) {
+ return -EINVAL;
+ }
+
+ char *new_summary = rstrdup(mreg, new_filename);
+
+ // Remove everything after extension
+ // TODO: Store the extension as a separate field in the ics file
+ char *dot = strrchr(new_summary, '.');
+ if (dot) {
+ *dot = '\0';
+ }
+
+ LOG("Summary is now %s", new_summary);
+ struct tree_node *child_node = get_node_by_path(mreg, old);
+ assert(child_node);
+ icalcomponent *child_ics =
+ get_icalcomponent_from_node(mreg, child_node);
+ assert(child_ics);
+
+ icalcomponent *new_parent_ics =
+ get_icalcomponent_from_node(mreg, new_parent_node);
+
+ struct tree_node *old_parent_node = child_node->parent;
+ icalcomponent *old_parent_ics =
+ get_icalcomponent_from_node(mreg, old_parent_node);
+
+ set_node_filename(child_node, new_filename);
+ update_summary(mreg, child_ics, child_node);
+
+ if (old_parent_node && old_parent_ics) {
+ remove_parent_child_relationship_from_component(old_parent_ics,
+ child_ics);
+ }
+
+ detach_tree_node(child_node);
+
+ if (new_parent_ics) {
+ move_node(new_parent_node, child_node);
+ write_parent_child_components(mreg, new_parent_node,
+ new_parent_ics, child_node,
+ child_ics);
+ }
+ else {
+ add_child(fuse_root, child_node);
+ }
+ return 0;
+}
+
+int
+delete_from_fuse_path(memory_region *mreg, const char *filepath)
+{
+ int res = 0;
+ struct tree_node *node = get_node_by_path(mreg, filepath);
+ if (!node) {
+ return -ENOENT;
+ }
+
+ icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+ if (node_is_directory(mreg, node, node_ics)) {
+ return -EISDIR;
+ }
+ char *filepath_original = get_vdir_filepath(mreg, node);
+
+ LOG("Deleting file %s, located at %s", filepath, filepath_original);
+
+ res = remove(filepath_original);
+ if (res == -1) {
+ return -EIO;
+ }
+
+ hashmap_remove(entries_vdir, filepath_original);
+ detach_tree_node(node);
+ free_tree(node);
+ return res;
+}
+
+char *
+get_node_path(memory_region *mreg, struct tree_node *node)
+{
+ if (!node->parent) {
+ return "/";
+ }
+
+ struct tree_node *curr = node;
+ char *result = NULL;
+ size_t result_len = 0;
+
+ FILE *stream = open_memstream(&result, &result_len);
+ assert(stream);
+
+ do {
+ if (!is_root_node(curr)) {
+ char *filename = get_node_filename(mreg, curr);
+ fprintf(stream, "/%s", filename);
+ }
+
+ curr = curr->parent;
+
+ } while (curr->parent);
+
+ fclose(stream);
+ if (result) {
+ region_register(mreg, result, free);
+ }
+
+ return result;
+}
+
+char *
+get_fuse_path_from_vdir(memory_region *mreg, const char *filepath)
+{
+ struct tree_node *node =
+ hashmap_get(entries_vdir, get_filename(filepath));
+
+ if (node) {
+ return get_node_path(mreg, node);
+ }
+ return NULL;
+}
+
+int
+delete_from_vdir_path(memory_region *mreg, const char *filepath)
+{
+ // Strip path, validate just the new basename
+ const char *keyname = get_filename(filepath);
+ struct agenda_entry *entry = hashmap_get(entries_vdir, keyname);
+ if (!entry) {
+ return -EIO;
+ }
+
+ // TODO: Fix so it finds by actual UUID, not filepath
+ struct tree_node *node = get_node_by_uuid(mreg, 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_vdir, keyname);
+ return 0;
+}
diff --git a/fuse_node.h b/fuse_node.h
@@ -0,0 +1,140 @@
+#ifndef fuse_node_h_INCLUDED
+#define fuse_node_h_INCLUDED
+
+#include "agenda_entry.h"
+#include "fuse_node_store.h"
+#include "hashmap.h"
+#include "ical_extra.h"
+#include "mregion.h"
+#include "path.h"
+#include "tree.h"
+#include "util.h"
+#include <dirent.h>
+#include <libical/ical.h>
+#include <pthread.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <uuid/uuid.h>
+#include <wordexp.h>
+
+struct tree_node *
+get_node_by_uuid(memory_region *mreg, const char *target_uuid);
+
+char *
+get_node_filename(memory_region *mreg, const struct tree_node *node);
+
+struct tree_node *
+get_node_by_path(memory_region *mreg, const char *path);
+
+bool
+is_root_node(const struct tree_node *node);
+
+bool
+node_is_directory(memory_region *mreg, const struct tree_node *node,
+ icalcomponent *node_ics);
+
+size_t
+write_ical_file(memory_region *mreg, const struct tree_node *node,
+ icalcomponent *ic);
+
+// Owner: ctx
+icalcomponent *
+get_icalcomponent_from_node(memory_region *mreg, const struct tree_node *n);
+
+int
+write_parent_child_components(memory_region *mreg,
+ const struct tree_node *parent,
+ icalcomponent *iparent,
+ const struct tree_node *child,
+ icalcomponent *ichild);
+
+// Writes ics component summary to the filename of the tree_node
+int
+update_summary(memory_region *mreg, icalcomponent *ics,
+ const struct tree_node *node);
+
+struct agenda_entry *
+parse_ics_to_agenda_entry(memory_region *mreg, const char *vdir_filepath);
+
+// agenda_entry is automatically cleaned up by mreg, use copy_agenda_entry
+// to take ownership!
+struct agenda_entry *
+load_agenda_entry_from_ics_file(memory_region *mreg, const char *filename);
+
+void
+load_root_node_tree();
+
+size_t
+load_agendafs_environment(char *vdir_env);
+
+// Returns comma separated list of categories for a file
+char *
+get_node_categories(memory_region *mreg, const struct tree_node *node);
+
+int
+delete_node_categories(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_categories(memory_region *mreg, const struct tree_node *node,
+ const char *new_categories, size_t s);
+
+const char *
+get_node_class(memory_region *mreg, const struct tree_node *node);
+
+int
+delete_node_class(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_class(memory_region *mreg, const struct tree_node *node,
+ const icalproperty_class new_class);
+
+uid_t
+get_node_uid(const struct tree_node *node);
+
+gid_t
+get_node_gid(const struct tree_node *node);
+
+ino_t
+get_node_ino(const struct tree_node *node);
+
+int
+insert_fuse_node_to_path(memory_region *mreg, const char *fuse_path,
+ struct tree_node *child_node,
+ icalcomponent *child_ics);
+
+int
+create_entry_from_fuse(memory_region *mreg, const char *fuse_path);
+
+int
+create_directory_from_fuse_path(memory_region *mreg, const char *fuse_path);
+
+void
+update_or_create_fuse_entry_from_vdir(memory_region *mreg,
+ const char *filepath_vdir);
+
+int
+delete_dir_from_fuse_path(memory_region *mreg, const char *filepath);
+
+int
+do_agenda_rename(memory_region *mreg, const char *, const char *);
+
+int
+delete_from_fuse_path(memory_region *mreg, const char *filepath);
+
+char *
+get_node_path(memory_region *mreg, struct tree_node *node);
+
+char *
+get_fuse_path_from_vdir(memory_region *mreg, const char *filepath);
+
+int
+delete_from_vdir_path(memory_region *mreg, const char *filepath);
+
+#endif // fuse_node_h_INCLUDED
diff --git a/fuse_node_store.c b/fuse_node_store.c
@@ -0,0 +1,74 @@
+#include "agenda_entry.h"
+#include "hashmap.h"
+#include "tree.h"
+#include "util.h"
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+char VDIR[256];
+
+// A structure of the current root
+struct tree_node *fuse_root = NULL;
+// ics entries, keys are the filename as stored in ICS_DIR
+// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
+struct hashmap *entries_vdir = NULL;
+
+// Copies vdir
+void
+set_vdir(const char *expanded_path)
+{
+ strlcpy(VDIR, expanded_path, sizeof(VDIR));
+}
+
+int
+set_node_filename(struct tree_node *node, const char *filename)
+{
+ struct agenda_entry *entry = node->data;
+ free(entry->filename);
+ entry->filename = xstrdup(filename);
+ return 0;
+}
+
+struct tree_node *
+get_fuse_node_from_vdir_name(const char *vdir_name)
+{
+ return hashmap_get(entries_vdir, vdir_name);
+}
+
+const struct agenda_entry *
+get_entry(const struct tree_node *node)
+{
+ assert(node->data);
+ return node->data;
+}
+
+char *
+get_vdir_filepath(memory_region *mreg, const struct tree_node *node)
+{
+ const struct agenda_entry *entry = get_entry(node);
+ return append_path(mreg, VDIR, entry->filename_vdir);
+}
+
+void
+delete_fuse_node(struct tree_node *node)
+{
+ const struct agenda_entry *e = get_entry(node);
+ hashmap_remove(entries_vdir, e->filename_vdir);
+ detach_tree_node(node);
+ free_tree(node);
+}
+
+// Copies agenda_entry
+struct tree_node *
+create_fuse_node(const struct agenda_entry *entry)
+{
+ struct agenda_entry *cpy = copy_agenda_entry(entry);
+
+ struct tree_node *new_node =
+ create_tree_node(cpy, (void *)free_agenda_entry);
+
+ hashmap_insert(entries_vdir, cpy->filename_vdir, new_node);
+
+ return new_node;
+}
+
diff --git a/fuse_node_store.h b/fuse_node_store.h
@@ -0,0 +1,41 @@
+#ifndef fuse_node_store_h_INCLUDED
+#define fuse_node_store_h_INCLUDED
+#include "agenda_entry.h"
+#include "hashmap.h"
+#include "util.h"
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+extern char VDIR[256];
+
+// A structure of the current root
+extern struct tree_node *fuse_root;
+// ics entries, keys are the filename as stored in ICS_DIR
+// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
+extern struct hashmap *entries_vdir;
+
+void
+set_vdir(const char *expanded_path);
+
+int
+set_node_filename(struct tree_node *node, const char *filename);
+
+struct tree_node *
+get_fuse_node_from_vdir_name(const char *vdir_name);
+
+const struct agenda_entry *
+get_entry(const struct tree_node *node);
+
+char *
+get_vdir_filepath(memory_region *mreg, const struct tree_node *node);
+
+int
+set_node_filename(struct tree_node *node, const char *filename);
+
+struct tree_node *
+create_fuse_node(const struct agenda_entry *entry);
+
+void
+delete_fuse_node(struct tree_node *node);
+
+#endif // fuse_node_store_h_INCLUDED
diff --git a/ical_extra.c b/ical_extra.c
@@ -263,3 +263,17 @@ set_parent_child_relationship_to_component(icalcomponent *parent,
icalcomponent_add_property(inner, child_related_to_parent);
}
}
+
+void
+icalcomponent_insert_description(memory_region *mreg, icalcomponent *ic,
+ const char *buf, size_t size, off_t offset)
+{
+
+ const char *old_desc = icalcomponent_get_description(ic);
+ if (!old_desc)
+ old_desc = "";
+
+ char *new_desc = rstrins(mreg, old_desc, offset, buf, size);
+ icalcomponent_set_description(ic, new_desc);
+}
+
diff --git a/ical_extra.h b/ical_extra.h
@@ -48,4 +48,7 @@ parse_ical_class(const char *input);
const char *
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);
#endif // ical_extra_h_INCLUDED
diff --git a/main.c b/main.c
@@ -1,8 +1,8 @@
#define FUSE_USE_VERSION 31
-#include "agenda_entry.h"
#include "fuse3/fuse_opt.h"
-#include "hashmap.h"
+#include "fuse_node.h"
+#include "fuse_node_store.h"
#include "ical_extra.h"
#include "mregion.h"
#include "tree.h"
@@ -555,14 +555,15 @@ handle_vdir_event(struct inotify_event *event)
rasprintf(mreg, &full_path, "%s/%s", VDIR, event->name);
if (event->mask & IN_DELETE) {
+ LOG("IN_DELETE HOOK");
delete_from_vdir_path(mreg, full_path);
}
else if (event->mask & IN_MODIFY) {
- LOG("RUNNING IN_MODIFY HOOK");
+ LOG("IN_MODIFY HOOK");
update_or_create_fuse_entry_from_vdir(mreg, full_path);
}
else if (event->mask & IN_CREATE) {
- LOG("RUNNING IN_CREATE HOOK");
+ LOG("IN_CREATE HOOK");
update_or_create_fuse_entry_from_vdir(mreg, full_path);
}
diff --git a/tree.c b/tree.c
@@ -124,3 +124,10 @@ detach_tree_node(struct tree_node *node)
node->parent = NULL;
return true;
}
+
+int
+move_node(struct tree_node *new_parent, struct tree_node *child)
+{
+ detach_tree_node(child);
+ return add_child(new_parent, child);
+}
diff --git a/tree.h b/tree.h
@@ -25,6 +25,9 @@ rcreate_tree_node(void *data, memory_region *m);
bool
node_has_children(const struct tree_node *node);
+int
+move_node(struct tree_node *new_parent, struct tree_node *child);
+
void
update_node_data(struct tree_node *node, void *data);