agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit e84a32c91a12240201572648bcdef3f7a3da189e
parent ca205e64d4091ba65db76fdc7200ed25ba9f8399
Author: Marc Coquand <marc@coquand.email>
Date: Wed, 16 Jul 2025 11:49:23 +0200
Parse files on demand instead of keeping everything in memory
This change refactors agendafs to use a memory_region structure
for everything that is temporary, and copy over data that is written
to global state.
Implements: https://todo.sr.ht/~marcc/agendafs/6
Diffstat:
| M | Makefile | | | 4 | ++-- |
| A | agenda_entry.c | | | 1228 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | agenda_entry.h | | | 202 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | hashmap.c | | | 12 | ++++++++++++ |
| M | hashmap.h | | | 30 | +++++++++++++++++++++--------- |
| D | journal_entry.c | | | 1333 | ------------------------------------------------------------------------------- |
| D | journal_entry.h | | | 190 | ------------------------------------------------------------------------------- |
| M | main.c | | | 204 | +++++++++++++++++++++++++++++++++++++++++++++++++------------------------------ |
| A | mregion.c | | | 204 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | mregion.h | | | 73 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | path.c | | | 47 | ++++++++++++++++------------------------------- |
| M | path.h | | | 23 | +++++++++++++---------- |
| M | tree.c | | | 19 | +++++++++++++++++-- |
| M | tree.h | | | 4 | ++++ |
| M | util.c | | | 5 | ++++- |
15 files changed, 1922 insertions(+), 1656 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,10 +5,10 @@ LIBS = `pkg-config uuid fuse3 libical --cflags --libs`
all: agendafs
-agendafs: main.c path.c util.c hashmap.c journal_entry.c tree.c
+agendafs: main.c path.c util.c hashmap.c agenda_entry.c tree.c
$(CC) $(CFLAGS) *.c -o mount_agendafs $(LIBS)
-debug: main.c path.c util.c hashmap.c journal_entry.c tree.c
+debug: main.c path.c util.c hashmap.c agenda_entry.c tree.c
$(CC) $(CFLAGS_DEBUG) $(DEBUG_FLAGS) *.c -o mount_agendafs-debug $(LIBS)
clean:
diff --git a/agenda_entry.c b/agenda_entry.c
@@ -0,0 +1,1228 @@
+#include "agenda_entry.h"
+#include "hashmap.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>
+
+const char *IS_DIRECTORY_PROPERTY = "X-CALDAVFS-ISDIRECTORY";
+char ICS_DIR[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_original_ics = NULL;
+
+struct agenda_entry *
+copy_agenda_entry(const struct agenda_entry *src)
+{
+ // Root node
+ if (!src) {
+ return NULL;
+ }
+ struct agenda_entry *copy = xmalloc(sizeof(struct agenda_entry));
+ copy->filename = xstrdup(src->filename);
+ copy->filename_original = xstrdup(src->filename_original);
+ copy->gid = src->gid;
+ copy->uid = src->uid;
+ return copy;
+}
+
+void
+free_agenda_entry(struct agenda_entry *entry)
+{
+ if (!entry)
+ return;
+ if (entry->filename)
+ free(entry->filename);
+ if (entry->filename_original)
+ free(entry->filename_original);
+ free(entry);
+ return;
+}
+
+struct tree_node *
+get_node_by_uuid(memory_region *mreg, const char *target_uuid)
+{
+ char *filename_original = NULL;
+
+ // Filenames are uid.ics, so we levarage that
+ size_t wRes =
+ rasprintf(mreg, &filename_original, "%s.ics", target_uuid);
+ assert(wRes != -1);
+
+ LOG("Looking for UID: '%s'", target_uuid);
+ LOG("Full filename: '%s'", filename_original);
+
+ struct tree_node *node =
+ hashmap_get(entries_original_ics, filename_original);
+ 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_directory_component(icalcomponent *component)
+{
+ icalcomponent *journal = icalcomponent_get_first_component(
+ component, ICAL_VJOURNAL_COMPONENT);
+
+ for (icalproperty *prop = icalcomponent_get_first_property(
+ journal, ICAL_RELATEDTO_PROPERTY);
+ prop != NULL; prop = icalcomponent_get_next_property(
+ journal, ICAL_RELATEDTO_PROPERTY)) {
+
+ const char *reltype =
+ icalproperty_get_parameter_as_string(prop, "RELTYPE");
+ if (reltype && strcasecmp(reltype, "CHILD") == 0) {
+ return true;
+ }
+ }
+ for (icalproperty *prop =
+ icalcomponent_get_first_property(journal, ICAL_X_PROPERTY);
+ prop != NULL;
+ prop = icalcomponent_get_next_property(journal, ICAL_X_PROPERTY)) {
+ LOG("FOUND X PROPERTY");
+
+ const char *prop_name = icalproperty_get_x_name(prop);
+ if (prop_name &&
+ strcmp(prop_name, IS_DIRECTORY_PROPERTY) == 0) {
+ const char *value =
+ icalproperty_get_value_as_string(prop);
+ LOG("FOUND DIRECTORY");
+ return value && strcmp(value, "YES") == 0;
+ }
+ }
+ return false;
+}
+
+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_original_filepath(memory_region *mreg, const struct tree_node *node)
+{
+ struct agenda_entry *entry = get_entry(node);
+ return append_path(mreg, ICS_DIR, entry->filename_original);
+}
+
+// Owner: ctx
+icalcomponent *
+parse_ics_file(memory_region *mreg, const char *filename)
+{
+
+ FILE *file = fopen(filename, "r");
+ LOG("filename is %s", filename);
+
+ struct stat st;
+
+ LOG("Stating...");
+ if (stat(filename, &st) != 0) {
+ LOG("Failed to stat file: %s", filename);
+ exit(1);
+ }
+
+ size_t size = st.st_size;
+ LOG("rmallocing...");
+ char *buffer = rmalloc(mreg, size + 1);
+
+ LOG("freading...");
+ size_t bytes_read = fread(buffer, 1, size, file);
+ buffer[bytes_read] = '\0';
+ fclose(file);
+
+ icalcomponent *component = ricalcomponent_new_from_string(mreg, buffer);
+
+ return component;
+}
+
+icaltimetype
+get_ical_now()
+{
+ return icaltime_from_timet_with_zone(time(NULL), 0,
+ icaltimezone_get_utc_timezone());
+}
+
+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);
+ }
+
+ LOG("Set last modified");
+
+ char *ical_str = ricalcomponent_as_ical_string_r(mreg, ic);
+
+ LOG("Got ical_str %s", ical_str);
+
+ int res = write_to_file(get_original_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_original_filepath(mreg, n);
+ // TODO: Cache intermediate results within context
+ icalcomponent *ic = parse_ics_file(mreg, orig_path);
+ return ic;
+}
+
+const char *
+get_parent_uid(icalcomponent *component)
+{
+ icalcomponent *inner =
+ icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
+
+ for (icalproperty *prop = icalcomponent_get_first_property(
+ inner, ICAL_RELATEDTO_PROPERTY);
+ prop != NULL; prop = icalcomponent_get_next_property(
+ inner, 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;
+}
+
+void
+remove_parent_child_relationship_from_component(icalcomponent *parent,
+ icalcomponent *child)
+{
+
+ const char *parent_uid = icalcomponent_get_uid(parent);
+
+ icalcomponent *inner =
+ icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
+
+ icalproperty *prop =
+ icalcomponent_get_first_property(inner, ICAL_RELATEDTO_PROPERTY);
+ while (prop != NULL) {
+ icalproperty *next = icalcomponent_get_next_property(
+ inner, ICAL_RELATEDTO_PROPERTY);
+ const char *value = icalproperty_get_relatedto(prop);
+ icalparameter *reltype = icalproperty_get_first_parameter(
+ prop, ICAL_RELTYPE_PARAMETER);
+
+ if (value && strcmp(value, parent_uid) == 0 && reltype &&
+ icalparameter_get_reltype(reltype) == ICAL_RELTYPE_PARENT) {
+ icalcomponent_remove_property(inner, prop);
+ icalproperty_free(prop);
+ }
+
+ prop = next;
+ }
+}
+
+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 *inner =
+ icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
+ if (inner) {
+ icalcomponent_add_property(inner, child_related_to_parent);
+ }
+}
+
+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_original = 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, ICS_DIR, 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;
+
+ LOG("Parsed %s to file %s", new_entry->filename_original,
+ new_entry->filename);
+ return new_entry;
+}
+
+void
+load_root_node_tree()
+{
+ LOG("Allocating journal entries");
+ entries_original_ics = hashmap_new(NULL);
+ fuse_root = create_tree_node(NULL, NULL);
+ memory_region *mreg = create_region();
+
+ LOG("Loading journal entries from: %s\n", ICS_DIR);
+
+ DIR *dir = opendir(ICS_DIR);
+ if (!dir) {
+ perror("opendir");
+ return;
+ }
+
+ struct dirent *entry;
+ LOG("Load files to root directory");
+ 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_original_ics,
+ new_entry->filename_original, new_node);
+
+ LOG("Inserted filename_original: %s, %s",
+ owned_entry->filename,
+ owned_entry->filename_original);
+ }
+ }
+ closedir(dir);
+ LOG("Set up directories according to parent-child");
+
+ size_t n_keys = 0;
+ char **keys = hashmap_get_keys(entries_original_ics, &n_keys);
+ for (size_t i = 0; i < n_keys; i++) {
+ const char *filename = keys[i];
+ struct tree_node *child =
+ hashmap_get(entries_original_ics, filename);
+
+ LOG("Parsing %s", filename);
+ icalcomponent *ic = get_icalcomponent_from_node(mreg, child);
+ LOG("Success");
+
+ struct agenda_entry *entry = get_entry(child);
+
+ const char *parent_uid = get_parent_uid(ic);
+ LOG("Looking up entry %s, %s", entry->filename_original,
+ entry->filename);
+ if (parent_uid) {
+ LOG("Has parent");
+
+ struct tree_node *parent =
+ get_node_by_uuid(mreg, parent_uid);
+ if (parent) {
+ struct agenda_entry *p_entry = parent->data;
+ LOG("Adding to parent %s", p_entry->filename);
+ 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 *ics_dir_env)
+{
+ wordexp_t expanded;
+ if (wordexp(ics_dir_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(ICS_DIR, expanded_path, sizeof(ICS_DIR));
+ 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; // Success
+}
+
+// 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;
+ 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 = rmalloc(mreg, s + 1);
+ strncpy(null_terminated_categories, new_categories, s);
+ null_terminated_categories[s] = '\0';
+
+ 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 *
+format_ical_class(const enum icalproperty_class iclass)
+{
+ if (iclass == ICAL_CLASS_PRIVATE) {
+ return "private";
+ }
+ else if (iclass == ICAL_CLASS_PUBLIC) {
+ return "public";
+ }
+ else if (iclass == ICAL_CLASS_CONFIDENTIAL) {
+ return "confidential";
+ }
+ return NULL;
+}
+
+const icalproperty_class
+parse_ical_class(const char *input)
+{
+ if (strcmp(input, "private") == 0) {
+ return ICAL_CLASS_PRIVATE;
+ }
+ else if (strcmp(input, "public") == 0) {
+ return ICAL_CLASS_PUBLIC;
+ }
+ else if (strcmp(input, "confidential") == 0) {
+ return ICAL_CLASS_CONFIDENTIAL;
+ }
+ return ICAL_CLASS_X;
+}
+
+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);
+}
+
+char *
+create_new_unique_ics_uid(memory_region *mreg)
+{
+ uuid_t uuid;
+ char uuid_str[37]; // UUIDs are 36 characters + null terminator
+ uuid_generate(uuid);
+ uuid_unparse(uuid, uuid_str);
+ char *res = NULL;
+ size_t wRes = rasprintf(mreg, &res, "%s-caldavfs", uuid_str);
+ assert(wRes != -1);
+
+ return res;
+}
+
+icalcomponent *
+create_vjournal_entry(memory_region *mreg, char *summary)
+{
+
+ icalcomponent *calendar = ricalcomponent_new_vcalendar(mreg);
+
+ icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
+ icalcomponent_add_property(calendar,
+ icalproperty_new_prodid("-//caldavfs//EN"));
+
+ // Step 2: Create a VJOURNAL entry
+ icalcomponent *journal = icalcomponent_new_vjournal();
+ char *id = create_new_unique_ics_uid(mreg);
+
+ icalcomponent_add_property(journal, icalproperty_new_uid(id));
+ icalcomponent_add_property(journal,
+ icalproperty_new_class(ICAL_CLASS_PRIVATE));
+
+ icalcomponent_add_property(
+ journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
+ icaltimezone_get_utc_timezone())));
+ icalcomponent_add_property(journal, icalproperty_new_summary(summary));
+ icalcomponent_add_property(journal, icalproperty_new_description(""));
+
+ icalcomponent_add_component(calendar, journal);
+
+ return calendar;
+}
+
+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;
+}
+
+icaltimetype
+get_last_modified(icalcomponent *component)
+{
+ icalproperty *last_modified_prop = icalcomponent_get_next_property(
+ component, ICAL_LASTMODIFIED_PROPERTY);
+
+ if (last_modified_prop) {
+ icaltimetype ical_lastmodified =
+ icalproperty_get_lastmodified(last_modified_prop);
+ return ical_lastmodified;
+ }
+ else {
+ return icalcomponent_get_dtstamp(component);
+ }
+}
+
+size_t
+get_entry_content_size(icalcomponent *component)
+{
+ size_t size =
+ snprintf(NULL, 0, "%s", icalcomponent_get_description(component));
+
+ assert(size != -1);
+ return size;
+}
+
+icalcomponent *
+create_vjournal_directory(memory_region *mreg, const char *summary)
+{
+
+ icalcomponent *calendar = icalcomponent_new_vcalendar();
+
+ icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
+ icalcomponent_add_property(calendar,
+ icalproperty_new_prodid("-//caldavfs//EN"));
+
+ icalcomponent *journal = icalcomponent_new_vjournal();
+ char *id = create_new_unique_ics_uid(mreg);
+
+ icalcomponent_add_property(journal, icalproperty_new_uid(id));
+
+ icalcomponent_add_property(
+ journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
+ icaltimezone_get_utc_timezone())));
+ icalcomponent_add_property(journal, icalproperty_new_summary(summary));
+
+ // Empty description; unused
+ icalcomponent_add_property(journal, icalproperty_new_description(""));
+
+ icalproperty *is_directory = icalproperty_new_x(IS_DIRECTORY_PROPERTY);
+ icalproperty_set_x_name(is_directory, IS_DIRECTORY_PROPERTY);
+ icalproperty_set_x(is_directory, "YES");
+ icalcomponent_add_property(journal, is_directory);
+
+ icalcomponent_add_component(calendar, journal);
+ return calendar;
+}
+
+int
+create_entry_from_fuse(memory_region *mreg, const char *fuse_path)
+{
+ // TODO: Move to path.c
+ 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_entry(mreg, new_basename);
+
+ new_entry->filename = xstrdup(new_basename);
+ if (asprintf(&new_entry->filename_original, "%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_original_ics, new_entry->filename_original,
+ new_node);
+
+ if (write_ical_file(mreg, new_node, new_component) != 0) {
+ LOG("Write to entry failed");
+ hashmap_remove(entries_original_ics,
+ new_entry->filename_original);
+
+ free_tree(new_node);
+ return -EIO;
+ }
+
+ LOG("DIRECTORY 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_original, "%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_original_ics, new_entry->filename_original,
+ new_node);
+
+ if (write_ical_file(mreg, new_node, new_component) != 0) {
+ LOG("Write to entry failed");
+ hashmap_remove(entries_original_ics,
+ new_entry->filename_original);
+
+ free_tree(new_node);
+ return -EIO;
+ }
+
+ LOG("DIRECTORY CREATED");
+ return 0;
+}
+
+void
+update_or_create_fuse_entry_from_original(memory_region *mreg,
+ const char *filepath_original)
+{
+ const char *filename_original = get_filename(filepath_original);
+ LOG("Filename original is %s", filename_original);
+
+ struct agenda_entry *updated_entry =
+ load_agenda_entry_from_ics_file(mreg, filename_original);
+
+ struct agenda_entry *updated_entry_owned =
+ copy_agenda_entry(updated_entry);
+
+ if (!updated_entry_owned) {
+ LOG("PARSING FAILED");
+ // TODO: Error code
+ return;
+ }
+
+ struct tree_node *new_or_updated_node =
+ hashmap_get(entries_original_ics, filename_original);
+
+ 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_original_ics,
+ updated_entry_owned->filename_original,
+ 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_original_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_original_ics, 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 -EIO;
+ }
+
+ icalcomponent *node_ics = get_icalcomponent_from_node(mreg, node);
+ if (node_is_directory(mreg, node, node_ics)) {
+ return -EISDIR;
+ }
+ char *filepath_original = get_original_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 == -1) {
+ return -EIO;
+ }
+
+ hashmap_remove(entries_original_ics, filepath_original);
+ detach_tree_node(node);
+ free_tree(node);
+ return res;
+}
+
+int
+delete_from_original_path(memory_region *mreg, const char *filepath)
+{
+ // Strip path, validate just the new basename
+ const char *keyname = strrchr(filepath, '/');
+ keyname++;
+ struct agenda_entry *entry = hashmap_get(entries_original_ics, 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_original_ics, keyname);
+ return 0;
+}
diff --git a/agenda_entry.h b/agenda_entry.h
@@ -0,0 +1,202 @@
+#ifndef agenda_entry_h_INCLUDED
+#define agenda_entry_h_INCLUDED
+
+#include "hashmap.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 agenda_entry {
+ // filename is full path relative to fuse directory
+ // I.E. hello world
+ char *filename;
+ // filename_original is the relative path to ICS_DIR
+ // I.E. 910319208nrao19p.ics
+ char *filename_original;
+ uid_t uid;
+ gid_t gid;
+};
+
+extern char ICS_DIR[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_original_ics;
+
+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_directory_component(icalcomponent *component);
+
+bool
+is_root_node(const struct tree_node *node);
+
+struct agenda_entry *
+get_entry(const struct tree_node *node);
+
+char *
+get_original_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);
+
+const char *
+get_parent_uid(icalcomponent *component);
+
+void
+remove_parent_child_relationship_from_component(icalcomponent *parent,
+ icalcomponent *child);
+
+void
+set_parent_child_relationship_to_component(icalcomponent *parent,
+ icalcomponent *child);
+
+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 *ics_dir_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 *
+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);
+
+icalcomponent *
+create_vjournal_entry(memory_region *mreg, char *summary);
+uid_t
+get_node_uid(const struct tree_node *node);
+
+gid_t
+get_node_gid(const struct tree_node *node);
+
+icaltimetype
+get_last_modified(icalcomponent *component);
+
+size_t
+get_entry_content_size(icalcomponent *component);
+
+icalcomponent *
+create_vjournal_directory(memory_region *mreg, const char *summary);
+
+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_original(memory_region *mreg,
+ const char *filepath_original);
+
+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);
+
+int
+delete_from_original_path(memory_region *mreg, const char *filepath);
+#endif // agenda_entry_h_INCLUDED
diff --git a/hashmap.c b/hashmap.c
@@ -1,6 +1,8 @@
// Taken from https://github.com/prismz/hashmap
#include "hashmap.h"
+#include "mregion.h"
+#include <assert.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
@@ -431,3 +433,13 @@ hashmap_free_keys(char **keys, size_t n_keys)
free(keys[i]);
free(keys);
}
+
+struct hashmap *
+rhashmap_new(memory_region *region, void (*val_free_func)(void *))
+{
+ struct hashmap *h = hashmap_new(val_free_func);
+ assert(h);
+
+ region_register(region, h, (void *)hashmap_free);
+ return h;
+}
diff --git a/hashmap.h b/hashmap.h
@@ -3,6 +3,7 @@
#ifndef HASHMAP_H
#define HASHMAP_H
+#include "mregion.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -54,13 +55,24 @@ struct hashmap {
void (*val_free_func)(void *);
};
-struct hashmap *hashmap_new(void (*val_free_func)(void *));
-void hashmap_free(struct hashmap *hm);
-int hashmap_resize(struct hashmap *hm);
-int hashmap_insert(struct hashmap *hm, const char *key, void *val);
-void *hashmap_get(struct hashmap *hm, const char *key);
-int hashmap_remove(struct hashmap *hm, const char *key);
-void hashmap_print(struct hashmap *hm);
-char **hashmap_get_keys(struct hashmap *hm, size_t *n_keys);
-void hashmap_free_keys(char **keys, size_t n_keys);
+struct hashmap *
+hashmap_new(void (*val_free_func)(void *));
+void
+hashmap_free(struct hashmap *hm);
+int
+hashmap_resize(struct hashmap *hm);
+int
+hashmap_insert(struct hashmap *hm, const char *key, void *val);
+void *
+hashmap_get(struct hashmap *hm, const char *key);
+int
+hashmap_remove(struct hashmap *hm, const char *key);
+void
+hashmap_print(struct hashmap *hm);
+char **
+hashmap_get_keys(struct hashmap *hm, size_t *n_keys);
+void
+hashmap_free_keys(char **keys, size_t n_keys);
+struct hashmap *
+rhashmap_new(memory_region *region, void (*val_free_func)(void *));
#endif /* HASHMAP_H */
diff --git a/journal_entry.c b/journal_entry.c
@@ -1,1333 +0,0 @@
-#include "journal_entry.h"
-
-#include "hashmap.h"
-#include "path.h"
-#include "tree.h"
-#include "util.h"
-#include <assert.h>
-#include <dirent.h>
-#include <errno.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>
-
-char ICS_DIR[256];
-
-static const char *IS_DIRECTORY_PROPERTY = "X-CALDAVFS-ISDIRECTORY";
-
-// tree of journal entries
-struct tree_node *fuse_tree_root = NULL;
-
-// journal_entry, keys are the filename as stored in ICS_DIR
-// I.E. bcb4c14b-8f3f-4a53-ad33-1f4499071a9m-caldavfs.ics
-struct hashmap *entries_original_ics = 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(const struct tree_node *node)
-{
- struct journal_entry *entry = node->data;
- return entry;
-}
-
-char *
-get_node_filename(const struct tree_node *node)
-{
- struct journal_entry *entry = node->data;
- if (entry == NULL)
- return "";
- return entry->filename;
-}
-
-icalcomponent *
-get_node_component(const struct tree_node *node)
-{
- struct journal_entry *entry = node->data;
- if (entry == NULL)
- return NULL;
- return entry->component;
-}
-
-struct tree_node **
-get_node_children(const struct tree_node *node)
-{
- struct tree_node **entries = node->children;
- return entries;
-}
-
-void
-detach_node_and_entry(struct tree_node *node)
-{
- struct tree_node *parent = node->parent;
- if (!parent || !parent->data) {
- return;
- }
- // TODO: Remove parent child relationship
- detach_tree_node(node);
-}
-
-struct tree_node *
-get_node_by_uuid(const char *target_uuid)
-{
- char *filename_original = NULL;
-
- // Filenames are uid.ics, so we levarage that
- size_t wRes = asprintf(&filename_original, "%s.ics", target_uuid);
- assert(wRes != -1);
-
- LOG("Looking for UID: '%s'", target_uuid);
- LOG("Full filename: '%s'", filename_original);
-
- struct tree_node *node =
- hashmap_get(entries_original_ics, filename_original);
- free(filename_original);
- LOG("Found it: %b", node != NULL);
- return node;
-}
-
-const char *
-get_node_ical_uuid(const struct tree_node *node)
-{
- struct journal_entry *entry = node->data;
- if (entry) {
- return icalcomponent_get_uid(entry->component);
- }
- return NULL;
-}
-bool
-is_root_node(const struct tree_node *node)
-{
- if (!node->data) {
- return true;
- }
- return false;
-}
-
-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 *inner =
- icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
- if (inner) {
- icalcomponent_add_property(inner, child_related_to_parent);
- }
-}
-void
-remove_parent_child_relationship_from_component(icalcomponent *parent,
- icalcomponent *child)
-{
-
- const char *parent_uid = icalcomponent_get_uid(parent);
-
- icalcomponent *inner =
- icalcomponent_get_first_component(child, ICAL_ANY_COMPONENT);
-
- icalproperty *prop =
- icalcomponent_get_first_property(inner, ICAL_RELATEDTO_PROPERTY);
- while (prop != NULL) {
- icalproperty *next = icalcomponent_get_next_property(
- inner, ICAL_RELATEDTO_PROPERTY);
- const char *value = icalproperty_get_relatedto(prop);
- icalparameter *reltype = icalproperty_get_first_parameter(
- prop, ICAL_RELTYPE_PARAMETER);
-
- if (value && strcmp(value, parent_uid) == 0 && reltype &&
- icalparameter_get_reltype(reltype) == ICAL_RELTYPE_PARENT) {
- icalcomponent_remove_property(inner, prop);
- icalproperty_free(prop);
- }
-
- prop = next;
- }
-}
-
-int
-add_child_to_node(struct tree_node *parent, struct tree_node *child)
-{
- // Root node, don't add relationship to component
- if (is_root_node(parent)) {
- LOG("Is root node, not adding relationship");
- add_child(parent, child);
- return 0;
- }
- else {
- LOG("Adding parent and child relation");
- 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);
- if (write_entry_to_ical_file(p_entry) != 0) {
- return -EIO;
- }
- if (write_entry_to_ical_file(c_entry) != 0) {
- return -EIO;
- }
-
- return add_child(parent, child);
- }
-}
-
-struct tree_node *
-get_node_by_path(const char *path)
-{
- if (strcmp(path, "/") == 0) {
- LOG("Is ROOT %s", path);
- return fuse_tree_root;
- }
-
- char *segments[255];
- size_t count = split_path(path, segments);
-
- LOG("Segments are defined");
-
- struct tree_node *current = fuse_tree_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(current->children[j]);
- if (strcmp(child_name, segment) == 0) {
- next = current->children[j];
- break;
- }
- }
- current = next;
- }
-
- free_segments(segments, count);
- return current;
-}
-
-// Full filepath of original ics file
-char *
-get_original_filepath(const struct journal_entry *entry)
-{
- return append_path(ICS_DIR, entry->filename_original);
-}
-
-icaltimetype
-get_ical_now()
-{
- return icaltime_from_timet_with_zone(time(NULL), 0,
- icaltimezone_get_utc_timezone());
-}
-
-void
-free_journal_entry(void *val)
-{
- struct journal_entry *entry = (struct journal_entry *)val;
- if (!entry)
- return;
- if (entry->component)
- icalcomponent_free(entry->component);
- if (entry->filename)
- free(entry->filename);
- if (entry->filename_original)
- free(entry->filename_original);
- free(entry);
-}
-
-struct journal_entry *
-get_entry_from_fuse_path(const char *path)
-{
- if (!path || path[0] != '/') {
- LOG("Invalid path: '%s'\n", path ? path : "NULL");
- return NULL;
- }
- const struct tree_node *node = get_node_by_path(path);
-
- if (!node || !node->data) {
- LOG("No entry found for key: '%s'\n", path);
- return NULL;
- }
-
- struct journal_entry *entry = node->data;
- char *ical_str = icalcomponent_as_ical_string_r(entry->component);
- LOG("Entry found: filename = '%s', original = '%s', path = '%s', has "
- "nonroot parent= '%b'\n%s",
- entry->filename, entry->filename_original, path,
- !is_root_node(node->parent), ical_str);
- free(ical_str);
- return entry;
-}
-
-// If it has a relationship type
-bool
-is_directory_component(icalcomponent *component)
-{
- icalcomponent *journal = icalcomponent_get_first_component(
- component, ICAL_VJOURNAL_COMPONENT);
-
- for (icalproperty *prop = icalcomponent_get_first_property(
- journal, ICAL_RELATEDTO_PROPERTY);
- prop != NULL; prop = icalcomponent_get_next_property(
- journal, ICAL_RELATEDTO_PROPERTY)) {
-
- const char *reltype =
- icalproperty_get_parameter_as_string(prop, "RELTYPE");
- if (reltype && strcasecmp(reltype, "CHILD") == 0) {
- return true;
- }
- }
- for (icalproperty *prop =
- icalcomponent_get_first_property(journal, ICAL_X_PROPERTY);
- prop != NULL;
- prop = icalcomponent_get_next_property(journal, ICAL_X_PROPERTY)) {
- LOG("FOUND X PROPERTY");
-
- const char *prop_name = icalproperty_get_x_name(prop);
- if (prop_name &&
- strcmp(prop_name, IS_DIRECTORY_PROPERTY) == 0) {
- const char *value =
- icalproperty_get_value_as_string(prop);
- LOG("FOUND DIRECTORY");
- return value && strcmp(value, "YES") == 0;
- }
- }
- return false;
-}
-
-// We assume it to be a directory if it has a property directory property
-// so or is a parent node
-bool
-node_is_directory(const struct tree_node *node)
-{
- 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.
- struct journal_entry *entry = node->data;
- return is_directory_component(entry->component);
- }
-}
-
-char *
-read_stream(char *s, size_t size, void *d)
-{
- return fgets(s, (int)size, (FILE *)d);
-}
-
-size_t
-parse_entry_content(struct journal_entry *entry, const char *buf, size_t size)
-{
-
- char *description = NULL;
- asprintf(&description, "%s", buf);
-
- icalcomponent_set_description(entry->component, description);
- return 0;
-}
-
-icalcomponent *
-parse_ics_file(const char *filename)
-{
-
- FILE *file = fopen(filename, "r");
- assert(file != NULL);
-
- struct stat st;
- stat(filename, &st);
- size_t size = st.st_size;
-
- char *buffer = xmalloc(size + 1);
- size_t bytes_read = fread(buffer, 1, size, file);
- buffer[bytes_read] = '\0';
- fclose(file);
-
- icalcomponent *component = icalcomponent_new_from_string(buffer);
- free(buffer);
-
- return component;
-}
-
-// We assume that there is only one inner component in the file
-int
-parse_ics_to_journal_entry_component(const char *filename,
- struct journal_entry *entry)
-{
- icalcomponent *component = parse_ics_file(filename);
- if (component == NULL) {
- LOG("No component");
- return -1;
- }
- // Verify if necessary?
- icalcomponent *journal =
- icalcomponent_get_first_real_component(component);
-
- // TODO: Support VTODOs, VEVENTs etc.
- if (journal == NULL ||
- icalcomponent_isa(journal) != ICAL_VJOURNAL_COMPONENT) {
- LOG("Not journal component");
- return -2;
- }
- if (icalcomponent_get_summary(component) == NULL) {
- LOG("No summary found");
- return -2;
- }
-
- if (icalcomponent_get_description(component) == NULL) {
- icalcomponent_set_description(component, "");
- }
-
- entry->component = component;
- return 0;
-}
-
-int
-move_node(struct tree_node *old_parent, struct tree_node *new_parent,
- struct tree_node *child)
-{
- struct journal_entry *child_entry = child->data;
-
- // Not root node
- if (!is_root_node(old_parent)) {
- LOG("Removing old node");
- struct journal_entry *old_parent_entry = old_parent->data;
- remove_parent_child_relationship_from_component(
- old_parent_entry->component, child_entry->component);
- write_entry_to_ical_file(old_parent_entry);
- }
- detach_tree_node(child);
- return add_child_to_node(new_parent, child);
-}
-
-// Returns -1 on failure, 0 on success
-int
-load_journal_entry_from_ics_file(const char *filename,
- struct journal_entry *entry)
-{
- path *filepath = append_path(ICS_DIR, filename);
- LOG("Filepath is %s", filepath);
-
- struct stat fileStat;
- if (stat(filepath, &fileStat) == -1) {
- LOG("Can not stat %s. skipping", filepath);
- free(filepath);
- return -1;
- }
-
- entry->filename_original = xstrdup(filename);
-
- if (parse_ics_to_journal_entry_component(filepath, entry) != 0) {
- LOG("Failed to parse entry: %s", filename);
- free(filepath);
- return -1;
- }
-
- entry->uid = fileStat.st_uid;
- entry->gid = fileStat.st_gid;
-
- // Make sure filename, which happens when
- // Two entries are created on the same minute
- // TODO: Check for duplicates
- if (is_directory_component(entry->component)) {
- if (asprintf(&entry->filename, "%s",
- icalcomponent_get_summary(entry->component)) ==
- -1) {
- LOG("Failed to write filename");
- return -1;
- };
- }
-
- // TODO: Enable custom fileformats by storing them in a
- // X-CALDAVFS-EXTENSION format
- else if (asprintf(&entry->filename, "%s",
- icalcomponent_get_summary(entry->component)) == -1) {
- LOG("Failed to write filename");
- return -1;
- };
- LOG("Parsed %s to file %s", entry->filename_original, entry->filename);
-
- free(filepath);
- return 0;
-}
-
-const char *
-get_parent_uid(icalcomponent *component)
-{
- icalcomponent *inner =
- icalcomponent_get_first_component(component, ICAL_ANY_COMPONENT);
-
- for (icalproperty *prop = icalcomponent_get_first_property(
- inner, ICAL_RELATEDTO_PROPERTY);
- prop != NULL; prop = icalcomponent_get_next_property(
- inner, 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;
-}
-
-void
-load_journal_entries()
-{
- LOG("Allocating journal entries");
- if (entries_original_ics) {
- hashmap_free(entries_original_ics);
- entries_original_ics = NULL;
- }
- if (fuse_tree_root) {
- free_tree(fuse_tree_root);
- fuse_tree_root = NULL;
- }
- entries_original_ics = hashmap_new(NULL);
- fuse_tree_root = create_tree_node(NULL, NULL);
-
- LOG("Loading journal entries from: %s\n", ICS_DIR);
-
- DIR *dir = opendir(ICS_DIR);
- if (!dir) {
- perror("opendir");
- return;
- }
-
- struct dirent *entry;
- LOG("Load files to root directory");
- while ((entry = readdir(dir))) {
- if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
-
- struct journal_entry *new_entry =
- xcalloc(1, sizeof(struct journal_entry));
-
- if (load_journal_entry_from_ics_file(entry->d_name,
- new_entry) != 0) {
- LOG("FAILED TO LOAD JOURNAL ENTRY");
- free_journal_entry(new_entry);
- continue;
- }
-
- struct tree_node *new_node =
- create_file_node(new_entry);
-
- hashmap_insert(entries_original_ics,
- new_entry->filename_original, new_node);
-
- LOG("Inserted filename_original: %s",
- new_entry->filename_original);
- }
- }
- closedir(dir);
- LOG("Set up directories according to parent-child");
-
- size_t n_keys = 0;
- char **keys = hashmap_get_keys(entries_original_ics, &n_keys);
- for (size_t i = 0; i < n_keys; i++) {
- const char *filename = keys[i];
- struct tree_node *child =
- hashmap_get(entries_original_ics, filename);
- struct journal_entry *entry = child->data;
-
- const char *parent_uid = get_parent_uid(entry->component);
- LOG("Looking up entry %s, %s", entry->filename_original,
- entry->filename);
- if (parent_uid) {
- LOG("Has parent");
-
- struct tree_node *parent = get_node_by_uuid(parent_uid);
- if (parent) {
- struct journal_entry *p_entry = parent->data;
- LOG("Adding to parent %s", p_entry->filename);
- detach_tree_node(child);
- add_child(parent, child);
- }
- else {
- LOG("COULD NOT FIND PARENT NODE: %s",
- parent_uid);
- }
- }
- else {
- add_child(fuse_tree_root, child);
- }
- }
- LOG("Inserted %zu entries", n_keys);
-
- for (int i = 0; i < n_keys; i++) {
- free(keys[i]);
- }
- free(keys);
-
- LOG("Done");
-}
-
-size_t
-write_entry_content(char *buf, struct journal_entry *entry)
-{
- // Allocate memory for the content
- size_t size = asprintf(&buf, "%s",
- icalcomponent_get_description(entry->component));
- assert(size != -1);
- return size;
-}
-
-size_t
-get_entry_content_size(struct journal_entry *entry)
-{
- size_t size = snprintf(NULL, 0, "%s",
- icalcomponent_get_description(entry->component));
-
- assert(size != -1);
- return size;
-}
-
-// Gets last modified, and if not exists, falls back to dtstamp
-icaltimetype
-get_last_modified(struct journal_entry *entry)
-{
- icalproperty *last_modified_prop = icalcomponent_get_next_property(
- entry->component, ICAL_LASTMODIFIED_PROPERTY);
-
- if (last_modified_prop) {
- icaltimetype ical_lastmodified =
- icalproperty_get_lastmodified(last_modified_prop);
- return ical_lastmodified;
- }
- else {
- return icalcomponent_get_dtstamp(entry->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;
-}
-
-struct journal_entry *
-copy_journal_entry(const struct journal_entry *src)
-{
- struct journal_entry *copy = xcalloc(1, sizeof(struct journal_entry));
-
- copy->filename = xstrdup(src->filename);
- copy->filename_original = xstrdup(src->filename_original);
- copy->component = icalcomponent_new_clone(src->component);
- assert(copy->component);
- copy->gid = src->gid;
- copy->uid = src->uid;
-
- return copy;
-}
-
-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; // Success
-}
-
-// Returns comma separated list of categories for a file
-char *
-get_node_categories(const struct tree_node *node)
-{
- const struct journal_entry *entry = node->data;
- if (!entry) {
- return NULL;
- }
-
- FILE *memstream = NULL;
- char *result_buffer = NULL;
- size_t buffer_size = 0;
-
- memstream = open_memstream(&result_buffer, &buffer_size);
-
- icalcomponent *inner = icalcomponent_get_inner(entry->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(const struct tree_node *node)
-{
- const struct journal_entry *entry = node->data;
- if (!entry) {
- return -ENOENT;
- }
-
- icalcomponent *inner = icalcomponent_get_inner(entry->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_entry_to_ical_file(entry);
-}
-
-int
-set_node_categories(const struct tree_node *node, const char *new_categories,
- size_t s)
-{
- const struct journal_entry *entry = node->data;
- if (!entry) {
- return -ENOENT;
- }
-
- icalcomponent *inner = icalcomponent_get_inner(entry->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_entry_to_ical_file(entry);
- }
-
- char *null_terminated_categories = xmalloc(s + 1);
- strncpy(null_terminated_categories, new_categories, s);
- null_terminated_categories[s] = '\0';
-
- icalproperty *new_cat_prop =
- icalproperty_new_categories(null_terminated_categories);
- icalcomponent_add_property(inner, new_cat_prop);
-
- return write_entry_to_ical_file(entry);
-}
-
-const char *
-format_ical_class(const enum icalproperty_class iclass)
-{
- if (iclass == ICAL_CLASS_PRIVATE) {
- return "private";
- }
- else if (iclass == ICAL_CLASS_PUBLIC) {
- return "public";
- }
- else if (iclass == ICAL_CLASS_CONFIDENTIAL) {
- return "confidential";
- }
- return NULL;
-}
-
-const icalproperty_class
-parse_ical_class(const char *input)
-{
- if (strcmp(input, "private") == 0) {
- return ICAL_CLASS_PRIVATE;
- }
- else if (strcmp(input, "public") == 0) {
- return ICAL_CLASS_PUBLIC;
- }
- else if (strcmp(input, "confidential") == 0) {
- return ICAL_CLASS_CONFIDENTIAL;
- }
- return ICAL_CLASS_X;
-}
-
-const char *
-get_node_class(const struct tree_node *node)
-{
- const struct journal_entry *entry = node->data;
- if (!entry) {
- return "";
- }
-
- icalcomponent *inner = icalcomponent_get_inner(entry->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(const struct tree_node *node)
-{
- const struct journal_entry *entry = node->data;
- if (!entry) {
- return -ENOENT;
- }
- icalcomponent *inner = icalcomponent_get_inner(entry->component);
- icalproperty *prop =
- icalcomponent_get_first_property(inner, ICAL_CLASS_PROPERTY);
-
- icalcomponent_remove_property(inner, prop);
-
- return write_entry_to_ical_file(entry);
-}
-
-int
-set_node_class(const struct tree_node *node, const icalproperty_class new_class)
-{
- const struct journal_entry *entry = node->data;
- if (!entry) {
- return -ENOENT;
- }
-
- icalcomponent *inner = icalcomponent_get_inner(entry->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_entry_to_ical_file(entry);
-}
-
-size_t
-write_entry_to_ical_file(const struct journal_entry *entry)
-{
-
- char *filepath = get_original_filepath(entry);
-
- // Always set last modified to the time we write
- icalproperty *prop = icalcomponent_get_first_property(
- entry->component, ICAL_LASTMODIFIED_PROPERTY);
- if (!prop) {
- prop = icalproperty_new_lastmodified(get_ical_now());
- icalcomponent_add_property(entry->component, prop);
- }
- else {
- icalproperty_set_lastmodified(prop, get_ical_now());
- }
-
- icalcomponent_set_status(entry->component, ICAL_STATUS_FINAL);
-
- const char *descr_prop =
- icalcomponent_get_description(entry->component);
- if (descr_prop != NULL && strcmp("", descr_prop) == 0) {
- icalproperty *ical_descr_prop =
- icalcomponent_get_first_property(
- icalcomponent_get_inner(entry->component),
- ICAL_DESCRIPTION_PROPERTY);
- icalcomponent_remove_property(
- icalcomponent_get_inner(entry->component), ical_descr_prop);
- }
-
- LOG("Set last modified");
-
- char *ical_str = icalcomponent_as_ical_string_r(entry->component);
-
- LOG("Got ical_str %s", ical_str);
-
- int res = write_to_file(filepath, ical_str);
-
- free(ical_str);
- free(filepath);
-
- return res;
-}
-
-size_t
-load_agendafs_environment(char *ics_dir_env)
-{
- wordexp_t expanded;
- if (wordexp(ics_dir_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(ICS_DIR, expanded_path, sizeof(ICS_DIR));
- wordfree(&expanded);
- return 0;
- }
-
- fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
- expanded_path);
- return -1;
-}
-
-int
-do_journal_entry_rename(const char *old, const char *new)
-{
- char *new_copy = xstrdup(new);
- const char *new_filename = get_filename(new_copy);
-
- const char *old_filename = get_filename(old);
- if (!old_filename)
- return -EINVAL;
-
- char *parent_path = NULL;
- get_parent_path(new, &parent_path);
- LOG("PARENT PATH: %s", parent_path);
- struct tree_node *new_parent_node = get_node_by_path(parent_path);
- free(parent_path);
-
- if (new_parent_node == NULL) {
- return -EINVAL;
- }
-
- char *new_summary = xstrdup(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 *entry_node = get_node_by_path(old);
- assert(entry_node);
-
- struct tree_node *old_parent_node = entry_node->parent;
-
- struct journal_entry *old_entry = entry_node->data;
- assert(old_entry);
-
- struct journal_entry *new_entry = copy_journal_entry(old_entry);
- free(parent_path);
-
- // TODO: Filename must be unique
- size_t wRes = asprintf(&new_entry->filename, "%s", new_filename);
- assert(wRes != -1);
-
- LOG("New filename is now %s", new_filename);
- LOG("Removed original entry from fuse");
- LOG("Inserting %s", new_entry->filename_original);
- hashmap_insert(entries_original_ics, new_entry->filename_original,
- entry_node);
-
- icalcomponent_set_summary(new_entry->component, new_summary);
- entry_node->data = new_entry;
- move_node(old_parent_node, new_parent_node, entry_node);
-
- free_journal_entry(old_entry);
- free(new_copy);
-
- return 0;
-}
-
-// uuid-caldavfs
-char *
-create_new_unique_ics_uid()
-{
- uuid_t uuid;
- char uuid_str[37]; // UUIDs are 36 characters + null terminator
- uuid_generate(uuid);
- uuid_unparse(uuid, uuid_str);
- char *res = NULL;
- size_t wRes = asprintf(&res, "%s-caldavfs", uuid_str);
- assert(wRes != -1);
-
- return res;
-}
-
-icalcomponent *
-create_vjournal_entry(char *summary)
-{
-
- icalcomponent *calendar = icalcomponent_new_vcalendar();
-
- icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
- icalcomponent_add_property(calendar,
- icalproperty_new_prodid("-//caldavfs//EN"));
-
- // Step 2: Create a VJOURNAL entry
- icalcomponent *journal = icalcomponent_new_vjournal();
- char *id = create_new_unique_ics_uid();
-
- icalcomponent_add_property(journal, icalproperty_new_uid(id));
- icalcomponent_add_property(journal,
- icalproperty_new_class(ICAL_CLASS_PRIVATE));
-
- icalcomponent_add_property(
- journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
- icaltimezone_get_utc_timezone())));
- icalcomponent_add_property(journal, icalproperty_new_summary(summary));
- icalcomponent_add_property(journal, icalproperty_new_description(""));
-
- icalcomponent_add_component(calendar, journal);
-
- return calendar;
-}
-
-icalcomponent *
-create_vjournal_directory(char *summary)
-{
-
- icalcomponent *calendar = icalcomponent_new_vcalendar();
-
- icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
- icalcomponent_add_property(calendar,
- icalproperty_new_prodid("-//caldavfs//EN"));
-
- icalcomponent *journal = icalcomponent_new_vjournal();
- char *id = create_new_unique_ics_uid();
-
- icalcomponent_add_property(journal, icalproperty_new_uid(id));
-
- icalcomponent_add_property(
- journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
- icaltimezone_get_utc_timezone())));
- icalcomponent_add_property(journal, icalproperty_new_summary(summary));
-
- // Empty description; unused
- icalcomponent_add_property(journal, icalproperty_new_description(""));
-
- icalproperty *is_directory = icalproperty_new_x(IS_DIRECTORY_PROPERTY);
- icalproperty_set_x_name(is_directory, IS_DIRECTORY_PROPERTY);
- icalproperty_set_x(is_directory, "YES");
- icalcomponent_add_property(journal, is_directory);
-
- icalcomponent_add_component(calendar, journal);
- return calendar;
-}
-
-bool
-match_by_uid(icalcomponent *component, const char *target_uuid)
-{
- const char *uid = icalcomponent_get_uid(component);
- return uid && strcmp(uid, target_uuid) == 0;
-}
-
-// TODO: Handle duplicates in filename
-int
-create_entry_from_fuse(const char *fuse_path)
-{
- // TODO: Move to path.c
- const char *new_basename = get_filename(fuse_path);
-
- path *without_extension = without_file_extension(new_basename);
-
- struct journal_entry *new_entry =
- xcalloc(1, sizeof(struct journal_entry));
- icalcomponent *vjournal_component =
- create_vjournal_entry(without_extension);
- free(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");
-
- struct tree_node *new_node = create_file_node(new_entry);
- char *parent_path = NULL;
- get_parent_path(fuse_path, &parent_path);
- LOG("Parent path is %s", parent_path);
- struct tree_node *parent = get_node_by_path(parent_path);
- free(parent_path);
-
- if (!parent) {
- LOG("Parent is not node");
- return -ENOENT;
- }
- if (!node_is_directory(parent)) {
- LOG("Parent is not directory ");
- return -ENOTDIR;
- }
-
- if (add_child_to_node(parent, new_node) != 0) {
- return -EIO;
- }
-
- hashmap_insert(entries_original_ics, new_entry->filename_original,
- new_node);
-
- return write_entry_to_ical_file(new_entry);
-}
-
-int
-create_directory_from_fuse_path(const char *fuse_path)
-{
- const char *entry_prefix = "/";
- if (strncmp(fuse_path, entry_prefix, strlen(entry_prefix)) != 0) {
- return -EINVAL;
- }
- char *new_basename = strrchr(fuse_path, '/');
- new_basename++;
-
- struct journal_entry *new_entry =
- xcalloc(1, sizeof(struct journal_entry));
- icalcomponent *vjournal_component =
- create_vjournal_directory(new_basename);
-
- new_entry->component = vjournal_component;
- new_entry->filename = strdup(new_basename);
- if (asprintf(&new_entry->filename_original, "%s.ics",
- icalcomponent_get_uid(new_entry->component)) == -1) {
- LOG("Failed to create journal entry");
- free_journal_entry(new_entry);
-
- return -ENOMEM;
- }
- LOG("Inserting vjournal directory");
-
- struct tree_node *new_node = create_file_node(new_entry);
-
- char *parent_path = NULL;
- get_parent_path(fuse_path, &parent_path);
- LOG("Parent path is %s", parent_path);
- struct tree_node *parent = get_node_by_path(parent_path);
- free(parent_path);
-
- if (!parent) {
- LOG("Parent is not node");
- return -ENOENT;
- }
- if (!node_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");
- return -EIO;
- }
-
- LOG("DIRECTORY CREATED");
- return 0;
-}
-
-// TODO: Add return status
-void
-update_or_create_fuse_entry_from_original(const char *filepath_original)
-{
- const char *filename_original = get_filename(filepath_original);
- LOG("Filename original is %s", filename_original);
-
- struct journal_entry *updated_entry =
- xcalloc(1, sizeof(struct journal_entry));
-
- if (load_journal_entry_from_ics_file(filename_original,
- updated_entry) != 0) {
- LOG("PARSING FAILED");
- return;
- }
-
- struct tree_node *new_or_updated_node =
- hashmap_get(entries_original_ics, filename_original);
-
- if (new_or_updated_node) {
- LOG("Updating existing entry");
- update_node_data(new_or_updated_node, updated_entry);
- }
- else {
- LOG("Creating new entry");
- new_or_updated_node = create_file_node(updated_entry);
- hashmap_insert(entries_original_ics,
- updated_entry->filename_original,
- new_or_updated_node);
- }
-
- const char *parent_uid = get_parent_uid(updated_entry->component);
- LOG("Looking up updated_entry %s, %s", updated_entry->filename_original,
- updated_entry->filename);
- if (parent_uid) {
- LOG("Has parent");
- struct tree_node *parent = get_node_by_uuid(parent_uid);
- if (parent) {
- struct tree_node *current_parent =
- new_or_updated_node->parent;
-
- if (current_parent != NULL &&
- strcmp(get_node_ical_uuid(current_parent),
- get_node_ical_uuid(parent)) == 0) {
- LOG("Already set, not moving");
- }
- else {
- move_node(new_or_updated_node->parent, parent,
- new_or_updated_node);
- }
- }
- else {
- LOG("Parent not found, adding to root");
- add_child(fuse_tree_root, new_or_updated_node);
- }
- }
- else {
- LOG("Does not have parent");
- detach_tree_node(new_or_updated_node);
- add_child(fuse_tree_root, new_or_updated_node);
- }
-
- LOG("File updated");
-}
-
-int
-delete_dir_from_fuse_path(const char *filepath)
-{
- int res = 0;
- struct tree_node *node = get_node_by_path(filepath);
- if (!node) {
- LOG("Node not found");
- return -EIO;
- }
- if (!node_is_directory(node)) {
- return -ENOTDIR;
- }
- if (node->child_count > 0) {
- return -ENOTEMPTY;
- }
-
- struct journal_entry *entry = get_entry(node);
- char *filepath_original = get_original_filepath(entry);
- 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_original_ics, filepath_original);
-
- detach_tree_node(node);
- free_tree(node);
- return res;
-}
-
-// Deletes original file too!
-int
-delete_from_fuse_path(const char *filepath)
-{
- int res = 0;
- struct tree_node *node = get_node_by_path(filepath);
- if (!node) {
- return -EIO;
- }
- if (node_is_directory(node)) {
- return -EISDIR;
- }
- struct journal_entry *entry = get_entry(node);
- char *filepath_original = get_original_filepath(entry);
- if (!filepath_original) {
- LOG("Entry not found");
- return -EIO;
- }
- LOG("Deleting file %s, located at %s", filepath, filepath_original);
-
- res = remove(filepath_original);
-
- hashmap_remove(entries_original_ics, filepath_original);
-
- detach_tree_node(node);
- free_tree(node);
- return res;
-}
-
-int
-delete_from_original_path(const char *filepath)
-{
- // Strip path, validate just the new basename
- const char *keyname = strrchr(filepath, '/');
- keyname++;
- struct journal_entry *entry =
- hashmap_get(entries_original_ics, keyname);
- if (!entry) {
- return -EIO;
- }
-
- // TODO: Fix so it finds by actual UUID, not filepath
- struct 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
- // which is the parent node.
- // Requires a change_parent function.
- detach_tree_node(node);
- free_tree(node);
- hashmap_remove(entries_original_ics, keyname);
- return 0;
-}
diff --git a/journal_entry.h b/journal_entry.h
@@ -1,190 +0,0 @@
-#ifndef JOURNAL_ENTRY_H
-#define JOURNAL_ENTRY_H
-#include "hashmap.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>
-
-// TODO: Move this to a file structure to a separate module, refactor all
-// operations to use the module exposed files
-// Such as
-// - rename
-// - remove
-// - get filename
-// et.c.
-struct journal_entry {
- // filename is full path relative to fuse directory
- // I.E. hello world
- char *filename;
- // filename_original is the relative path to ICS_DIR
- // I.E. 910319208nrao19p.ics
- char *filename_original;
- uid_t uid;
- gid_t gid;
-
- icalcomponent *component;
-};
-
-extern char ICS_DIR[256];
-
-extern struct tree_node *fuse_tree_root;
-
-// journal_entry
-extern struct hashmap *entries_original_ics;
-
-// Full filepath of original ics file
-char *
-get_original_filepath(const struct journal_entry *entry);
-
-void
-free_journal_entry(void *val);
-
-const char *
-get_filename(const char *full_path);
-
-struct journal_entry *
-get_entry_from_fuse_path(const char *path);
-
-icalcomponent *
-parse_ics_file(const char *filename);
-
-// We assume that there is only one entry in the file
-int
-parse_ics_to_journal_entry_component(const char *filename,
- struct journal_entry *entry);
-
-int
-do_journal_entry_rename(const char *old, const char *new_file);
-
-size_t
-parse_entry_content(struct journal_entry *entry, const char *buf, size_t size);
-
-size_t
-write_entry_to_ical_file(const struct journal_entry *entry);
-
-// Returns -1 on failure, 0 on success
-int
-load_journal_entry_from_ics_file(const char *filename,
- struct journal_entry *entry);
-
-// We assume that there is only one entry in the file
-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(const struct tree_node *node);
-
-int
-delete_dir_from_fuse_path(const char *filepath);
-
-bool
-node_is_directory(const struct tree_node *node);
-
-int
-delete_from_fuse_path(const char *filepath);
-
-int
-create_entry_from_fuse(const char *filename);
-int
-delete_from_original_path(const char *filepath);
-
-// Returns -1 on failure, 0 on success
-int
-load_journal_entry_from_ics_file(const char *filename,
- struct journal_entry *entry);
-
-icalcomponent *
-create_vjournal_directory(char *summary);
-
-// Initializes
-// entries_original_key
-// entries_fuse_key
-// Should only be run once
-void
-load_journal_entries();
-
-icaltimetype
-get_ical_now();
-
-size_t
-write_entry_content(char *buf, struct journal_entry *entry);
-
-size_t
-get_entry_content_size(struct journal_entry *entry);
-
-// Gets last modified, and if not exists, falls back to dtstamp
-icaltimetype
-get_last_modified(struct journal_entry *entry);
-
-// TODO: Probably better to calculate this on demand
-int
-build_today_hashmap();
-
-struct tree_node *
-get_node_by_path(const char *path);
-
-const icalproperty_class
-parse_ical_class(const char *);
-
-const char *
-get_node_class(const struct tree_node *node);
-
-int
-delete_node_class(const struct tree_node *node);
-
-int
-set_node_class(const struct tree_node *node,
- const icalproperty_class new_class);
-
-char *
-get_node_categories(const struct tree_node *);
-
-int
-delete_node_categories(const struct tree_node *);
-
-int
-set_node_categories(const struct tree_node *, const char *, size_t s);
-
-int
-build_notes_hashmap();
-
-size_t
-load_agendafs_environment(char *dir);
-char *
-create_new_unique_ics_uid();
-
-icalcomponent *
-create_vjournal_entry(char *summary);
-
-int
-create_directory_from_fuse_path(const char *fuse_path);
-
-uid_t
-get_node_uid(const struct tree_node *node);
-
-gid_t
-get_node_gid(const struct tree_node *node);
-
-void
-update_or_create_fuse_entry_from_original(const char *filename);
-
-int
-update_fuse_entry_delete(const char *file);
-
-#endif // JOURNAL_ENTRY_H
-
diff --git a/main.c b/main.c
@@ -1,7 +1,8 @@
+#include "mregion.h"
#define FUSE_USE_VERSION 31
+#include "agenda_entry.h"
#include "hashmap.h"
-#include "journal_entry.h"
#include "tree.h"
#include "util.h"
#include <dirent.h>
@@ -31,12 +32,15 @@ static int
journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{
pthread_mutex_lock(&entries_mutex);
+ memory_region *mreg = create_region();
LOG("STAT %s", path);
+ LOG("memset");
memset(stbuf, 0, sizeof(struct stat));
int ret_code = 0;
+ LOG("strcmp");
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0444;
stbuf->st_nlink = 2;
@@ -47,15 +51,17 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
goto unlock_and_return;
}
- const struct tree_node *node = get_node_by_path(path);
+ LOG("Getting node");
+ const struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
LOG("Entry not found");
ret_code = -ENOENT;
goto unlock_and_return;
}
- struct journal_entry *entry = get_entry(node);
- if (node_is_directory(node)) {
+ LOG("icalcomponent from node");
+ icalcomponent *ic = get_icalcomponent_from_node(mreg, node);
+ if (node_is_directory(mreg, node, ic)) {
stbuf->st_mode = S_IFDIR | 0444;
stbuf->st_uid = get_node_uid(node);
stbuf->st_gid = get_node_gid(node);
@@ -70,12 +76,11 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
stbuf->st_nlink = 1;
}
- int size = get_entry_content_size(entry);
+ int size = get_entry_content_size(ic);
stbuf->st_size = (off_t)size;
- LOG("Entry found: %s", entry->filename);
- icaltimetype ical_last_modified = get_last_modified(entry);
+ icaltimetype ical_last_modified = get_last_modified(ic);
time_t last_modified = icaltime_as_timet(ical_last_modified);
@@ -87,6 +92,7 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
unlock_and_return:
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return ret_code;
}
@@ -104,6 +110,7 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
{
pthread_mutex_lock(&entries_mutex);
+ memory_region *mreg = create_region();
LOG("READDIR %s", path);
filler(buf, ".", NULL, 0, 0);
filler(buf, "..", NULL, 0, 0);
@@ -112,11 +119,11 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
if (strcmp(path, "/") == 0) {
LOG("IS ROOT");
- for (size_t i = 0; i < fuse_tree_root->child_count; i++) {
+ for (size_t i = 0; i < fuse_root->child_count; i++) {
- const struct tree_node *node =
- fuse_tree_root->children[i];
- struct journal_entry *entry = node->data;
+ const struct tree_node *node = fuse_root->children[i];
+ icalcomponent *ic =
+ get_icalcomponent_from_node(mreg, node);
struct stat st = {0};
// TODO: Fix
@@ -126,21 +133,21 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
st.st_uid = get_node_uid(node);
st.st_gid = get_node_gid(node);
- if (node_is_directory(node)) {
+ if (node_is_directory(mreg, node, ic)) {
st.st_mode = S_IFDIR | 0755;
}
- filler(buf, entry->filename, &st, 0, 0);
+ filler(buf, get_node_filename(mreg, node), &st, 0, 0);
}
}
else {
- const struct tree_node *node = get_node_by_path(path);
+ const struct tree_node *node = get_node_by_path(mreg, path);
if (node) {
for (size_t i = 0; i < node->child_count; i++) {
- LOG("FOUND CHILD");
const struct tree_node *child =
node->children[i];
- struct journal_entry *entry = child->data;
+ icalcomponent *ic =
+ get_icalcomponent_from_node(mreg, child);
struct stat st = {0};
// TODO: Fix
@@ -150,42 +157,50 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
st.st_uid = get_node_uid(child);
st.st_gid = get_node_gid(child);
- if (node_is_directory(child)) {
+ if (node_is_directory(mreg, child, ic)) {
st.st_mode = S_IFDIR | 0755;
}
- filler(buf, entry->filename, &st, 0, 0);
+ filler(buf, get_node_filename(mreg, child), &st,
+ 0, 0);
}
}
else {
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return -ENOENT;
}
}
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return 0;
}
static int
journal_open(const char *path, struct fuse_file_info *fi)
{
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
LOG("Opening journal for %s", path);
- struct journal_entry *entry = get_entry_from_fuse_path(path);
+ const struct tree_node *node = get_node_by_path(mreg, path);
pthread_mutex_unlock(&entries_mutex);
- return entry ? 0 : -ENOENT;
+ rfree_all(mreg);
+ return node ? 0 : -ENOENT;
}
static int
journal_mkdir(const char *path, mode_t mode)
{
+
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
LOG("MKDIR %s", path);
- if (create_directory_from_fuse_path(path) != 0) {
+ if (create_directory_from_fuse_path(mreg, path) != 0) {
pthread_mutex_unlock(&entries_mutex);
return -EIO;
};
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return 0;
}
@@ -194,18 +209,19 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
int ret_code = 0;
+
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
LOG("READ on path %s", path);
- struct journal_entry *entry = get_entry_from_fuse_path(path);
- if (!entry) {
+ const struct tree_node *n = get_node_by_path(mreg, path);
+ if (!n) {
ret_code = -ENOENT;
goto unlock_and_return;
}
- LOG("entry = %p", entry);
- LOG("entry->component = %p", entry->component);
- const char *description =
- icalcomponent_get_description(entry->component);
+ LOG("entry = %p", n);
+ icalcomponent *ic = get_icalcomponent_from_node(mreg, n);
+ const char *description = icalcomponent_get_description(ic);
if (!description) {
description = "";
}
@@ -236,6 +252,7 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
unlock_and_return:
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return ret_code;
}
@@ -243,29 +260,34 @@ static int
journal_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
- if (size > 60 * 1024) {
- return -EFBIG;
- }
+ LOG("WRITE %s, %zu, %zu", buf, size, offset);
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
- struct journal_entry *entry = get_entry_from_fuse_path(path);
- if (!entry) {
+ const struct tree_node *node = get_node_by_path(mreg, path);
+ if (!node) {
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return -ENOENT;
}
- LOG("Parsing entry content");
- int ret = parse_entry_content(entry, buf, size);
- if (ret != 0) {
- pthread_mutex_unlock(&entries_mutex);
- return ret;
- }
+ icalcomponent *ic = get_icalcomponent_from_node(mreg, node);
+ const char *old_desc = icalcomponent_get_description(ic);
+ if (!old_desc)
+ old_desc = "";
+
+ char *new_desc = rstrins(mreg, old_desc, offset, buf, size);
- if (write_entry_to_ical_file(entry) != 0) {
+ icalcomponent_set_description(ic, new_desc);
+
+ if (write_ical_file(mreg, node, ic) != 0) {
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return -EIO;
}
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
+
return size;
}
@@ -273,60 +295,60 @@ journal_write(const char *path, const char *buf, size_t size, off_t offset,
static int
journal_unlink(const char *file)
{
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
LOG("DELETE %s", file);
int res = 0;
- res = delete_from_fuse_path(file);
- if (res != 0) {
- LOG("Failed to delete file");
- pthread_mutex_unlock(&entries_mutex);
- return -EIO;
- }
-
- LOG("Delete successful");
+ res = delete_from_fuse_path(mreg, file);
+ LOG("Delete");
pthread_mutex_unlock(&entries_mutex);
- return 0;
+ rfree_all(mreg);
+ return res;
}
static int
journal_rmdir(const char *file)
{
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
LOG("DELETE %s", file);
int res = 0;
- res = delete_dir_from_fuse_path(file);
- if (res != 0) {
- LOG("Failed to delete file");
- pthread_mutex_unlock(&entries_mutex);
- return res;
- }
+ res = delete_dir_from_fuse_path(mreg, file);
LOG("Delete successful");
pthread_mutex_unlock(&entries_mutex);
- return 0;
+ rfree_all(mreg);
+ return res;
}
static int
journal_rename(const char *old, const char *new, unsigned int flags)
{
+
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
LOG("RENAME %s -> %s", old, new);
- int res = do_journal_entry_rename(old, new);
+
+ int res = do_agenda_rename(mreg, old, new);
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return res;
}
static int
journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
{
+
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
LOG("CREATE %s", filename);
// TODO: Handle mode and fuse_file_info
- create_entry_from_fuse(filename);
+ create_entry_from_fuse(mreg, filename);
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return 0;
}
@@ -334,33 +356,38 @@ static int
journal_removexattr(const char *path, const char *header)
{
LOG("REMOVEXATTR '%s' '%s'", path, header);
+ memory_region *mreg = create_region();
if (strcmp(header, "user.categories") == 0) {
pthread_mutex_lock(&entries_mutex);
- struct tree_node *node = get_node_by_path(path);
+ struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
pthread_mutex_unlock(&entries_mutex);
+
+ rfree_all(mreg);
return -ENONET;
}
- int status = set_node_categories(node, "", 0);
+ int status = set_node_categories(mreg, node, "", 0);
pthread_mutex_unlock(&entries_mutex);
return status;
}
else if (strcmp(header, "user.class") == 0) {
pthread_mutex_lock(&entries_mutex);
- struct tree_node *node = get_node_by_path(path);
+ struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return -ENONET;
}
- int status = delete_node_class(node);
+ int status = delete_node_class(mreg, node);
pthread_mutex_unlock(&entries_mutex);
return status;
}
+ rfree_all(mreg);
return -EINVAL;
}
@@ -371,26 +398,30 @@ journal_setxattr(const char *path, const char *header,
{
LOG("SETXATTR '%s' '%s' '%zu' '%s' '%d'", path, header, s,
new_attributes, flags);
+ memory_region *mreg = create_region();
if (strcmp(header, "user.categories") == 0) {
pthread_mutex_lock(&entries_mutex);
- struct tree_node *node = get_node_by_path(path);
+ struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return -ENONET;
}
- int status = set_node_categories(node, new_attributes, s);
+ int status = set_node_categories(mreg, node, new_attributes, s);
pthread_mutex_unlock(&entries_mutex);
return status;
}
else if (strcmp(header, "user.class") == 0) {
pthread_mutex_lock(&entries_mutex);
- struct tree_node *node = get_node_by_path(path);
+ struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
pthread_mutex_unlock(&entries_mutex);
+
+ rfree_all(mreg);
return -ENONET;
}
@@ -399,15 +430,19 @@ journal_setxattr(const char *path, const char *header,
if (iclass == ICAL_CLASS_X) {
pthread_mutex_unlock(&entries_mutex);
+
+ rfree_all(mreg);
return -EINVAL;
}
- int status = set_node_class(node, iclass);
+ int status = set_node_class(mreg, node, iclass);
pthread_mutex_unlock(&entries_mutex);
+ rfree_all(mreg);
return status;
}
+ rfree_all(mreg);
return -EINVAL;
}
@@ -419,10 +454,12 @@ journal_listxattr(const char *path, char *list, size_t size)
char *membuffer = NULL;
size_t membuffer_len = 0;
+ memory_region *mreg = create_region();
pthread_mutex_lock(&entries_mutex);
- struct tree_node *node = get_node_by_path(path);
+ struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
pthread_mutex_unlock(&entries_mutex);
+
return -ENONET;
}
@@ -431,14 +468,14 @@ journal_listxattr(const char *path, char *list, size_t size)
pthread_mutex_unlock(&entries_mutex);
return -EIO;
}
- char *cats = get_node_categories(node);
+ char *cats = get_node_categories(mreg, node);
if (cats != NULL && strcmp("", cats) != 0) {
fprintf(stream, "user.categories");
fputc('\0', stream);
}
free(cats);
- const char *iclass = get_node_class(node);
+ const char *iclass = get_node_class(mreg, node);
if (iclass != NULL && strcmp("", iclass) != 0) {
fprintf(stream, "user.class");
fputc('\0', stream);
@@ -470,17 +507,19 @@ static int
journal_getxattr(const char *path, const char *header, char *buf, size_t s)
{
LOG("GETXATTR '%s' '%s' '%zu'\n", path, header, s);
+ fuse_get_context();
+ memory_region *mreg = create_region();
if (strcmp(header, "user.categories") == 0) {
pthread_mutex_lock(&entries_mutex);
- struct tree_node *node = get_node_by_path(path);
+ struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
pthread_mutex_unlock(&entries_mutex);
return -ENONET;
}
- const char *cats = get_node_categories(node);
+ const char *cats = get_node_categories(mreg, node);
pthread_mutex_unlock(&entries_mutex);
if (!cats || *cats == '\0') {
@@ -499,14 +538,14 @@ journal_getxattr(const char *path, const char *header, char *buf, size_t s)
}
else if (strcmp(header, "user.class") == 0) {
pthread_mutex_lock(&entries_mutex);
- struct tree_node *node = get_node_by_path(path);
+ struct tree_node *node = get_node_by_path(mreg, path);
if (!node) {
pthread_mutex_unlock(&entries_mutex);
return -ENONET;
}
- const char *c = get_node_class(node);
+ const char *c = get_node_class(mreg, node);
pthread_mutex_unlock(&entries_mutex);
if (!c || *c == '\0') {
return -ENODATA;
@@ -568,24 +607,34 @@ watch_ics_dir(void *arg)
if (event->len && strstr(event->name, ".ics")) {
LOG("Detected change in ICS file: %s\n",
event->name);
+ memory_region *mreg = create_region();
if (event->mask & IN_DELETE) {
pthread_mutex_lock(&entries_mutex);
- delete_from_original_path(full_path);
+ delete_from_original_path(mreg,
+ full_path);
+ rfree_all(mreg);
pthread_mutex_unlock(&entries_mutex);
}
else if (event->mask & IN_MODIFY) {
pthread_mutex_lock(&entries_mutex);
+ LOG("RUNNING IN_MODIFY HOOK");
update_or_create_fuse_entry_from_original(
- full_path);
+ mreg, full_path);
+ rfree_all(mreg);
pthread_mutex_unlock(&entries_mutex);
}
else if (event->mask & IN_CREATE) {
pthread_mutex_lock(&entries_mutex);
+ LOG("RUNNING IN_CREATE HOOK");
update_or_create_fuse_entry_from_original(
- full_path);
+ mreg, full_path);
+ rfree_all(mreg);
pthread_mutex_unlock(&entries_mutex);
}
+ else {
+ rfree_all(mreg);
+ }
}
free(full_path);
@@ -706,8 +755,7 @@ main(int argc, char *argv[])
return 1;
}
- fuse_tree_root = create_tree_node(NULL, NULL);
- load_journal_entries();
+ load_root_node_tree();
int ret = fuse_main(args.argc, args.argv, &journal_oper, NULL);
LOG("Cleaning up");
@@ -716,7 +764,7 @@ main(int argc, char *argv[])
LOG("Pthread freed");
hashmap_free(entries_original_ics);
- free_tree(fuse_tree_root);
+ free_tree(fuse_root);
LOG("Hashmap and tree freed");
LOG("Regexp freed");
diff --git a/mregion.c b/mregion.c
@@ -0,0 +1,204 @@
+#include "mregion.h"
+#include "util.h"
+#include <assert.h>
+#include <libical/ical.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef void (*free_func_t)(void *);
+
+memory_region *
+create_region(void)
+{
+ memory_region *region = xmalloc(sizeof(memory_region));
+ region->head = NULL;
+ return region;
+}
+
+void
+region_register(memory_region *region, void *ptr, free_func_t free_func)
+{
+ assert(free_func);
+ memory_block *block = xmalloc(sizeof(memory_block));
+ block->ptr = ptr;
+ block->free_func = free_func;
+ block->next = region->head;
+ region->head = block;
+}
+
+void *
+rmalloc(memory_region *region, size_t size)
+{
+ void *ptr = xmalloc(size);
+ region_register(region, ptr, free);
+ return ptr;
+}
+
+void
+rfree_all(memory_region *region)
+{
+ if (!region)
+ return;
+ memory_block *block = region->head;
+ while (block) {
+ memory_block *next = block->next;
+ if (block->free_func)
+ block->free_func(block->ptr);
+ free(block);
+ block = next;
+ }
+ region->head = NULL;
+ free(region);
+}
+
+// String functions
+char *
+rstrdup(memory_region *region, const char *str)
+{
+ char *copy = xstrdup(str);
+ region_register(region, copy, free);
+ return copy;
+}
+
+char *
+rstrins(memory_region *region, const char *str, size_t offset, const char *buf,
+ size_t size)
+{
+ size_t str_len = strlen(str);
+
+ size_t previous_new_len = (offset <= str_len) ? offset : str_len;
+
+ char *result = NULL;
+ size_t result_len = 0;
+
+ FILE *stream = open_memstream(&result, &result_len);
+ assert(stream);
+
+ fwrite(str, 1, previous_new_len, stream);
+ fwrite(buf, 1, size, stream);
+
+ fclose(stream);
+
+ region_register(region, result, free);
+
+ return result;
+}
+
+int
+rasprintf(memory_region *region, char **strp, const char *fmt, ...)
+{
+ // Calculate length
+ va_list args;
+ va_start(args, fmt);
+ int len = vsnprintf(NULL, 0, fmt, args);
+ va_end(args);
+
+ if (len < 0) {
+ *strp = NULL;
+ return -1;
+ }
+
+ char *buffer = rmalloc(region, len + 1);
+ va_start(args, fmt);
+ int ret = vsnprintf(buffer, len + 1, fmt, args);
+ va_end(args);
+ if (ret < 0) {
+ *strp = NULL;
+ return -1;
+ }
+ *strp = buffer;
+ return ret;
+}
+
+void
+rappend(char **dest, const char *fmt, ...)
+{
+ va_list args;
+
+ // Determine formatted size
+ va_start(args, fmt);
+ int add_len = vsnprintf(NULL, 0, fmt, args);
+ va_end(args);
+
+ if (add_len < 0)
+ return;
+
+ size_t old_len = *dest ? strlen(*dest) : 0;
+ char *newbuf = realloc(*dest, old_len + add_len + 1);
+ if (!newbuf)
+ return;
+
+ // Write formatted string to end
+ va_start(args, fmt);
+ vsnprintf(newbuf + old_len, add_len + 1, fmt, args);
+ va_end(args);
+
+ *dest = newbuf;
+}
+
+// iCal functions
+
+icalparser *
+ricalparser_new(memory_region *region)
+{
+ icalparser *p = icalparser_new();
+ region_register(region, p, (void *)icalparser_free);
+ return p;
+}
+
+void
+ricalcomponent_free(icalcomponent *ic)
+{
+ if (ic)
+ icalcomponent_free(ic);
+}
+
+icalcomponent *
+ricalcomponent_empty(memory_region *region)
+{
+ icalcomponent *ic = NULL;
+ region_register(region, ic, (void *)ricalcomponent_free);
+ return ic;
+}
+
+icalcomponent *
+ricalcomponent_new_clone(memory_region *region, icalcomponent *ic)
+{
+ icalcomponent *nic = icalcomponent_new_clone(ic);
+ region_register(region, nic, (void *)ricalcomponent_free);
+ return nic;
+}
+
+icalcomponent *
+ricalcomponent_new_from_string(memory_region *region, const char *ic)
+{
+ icalcomponent *nic = icalcomponent_new_from_string(ic);
+ region_register(region, nic, (void *)ricalcomponent_free);
+ return nic;
+}
+
+char *
+ricalcomponent_as_ical_string(memory_region *region, icalcomponent *ic)
+{
+ char *c = icalcomponent_as_ical_string(ic);
+ region_register(region, c, free);
+ return c;
+}
+
+char *
+ricalcomponent_as_ical_string_r(memory_region *region, icalcomponent *ic)
+{
+ char *c = icalcomponent_as_ical_string_r(ic);
+ region_register(region, c, free);
+ return c;
+}
+
+icalcomponent *
+ricalcomponent_new_vcalendar(memory_region *region)
+{
+ icalcomponent *new_comp = icalcomponent_new_vcalendar();
+ region_register(region, new_comp, (void *)ricalcomponent_free);
+ return new_comp;
+}
diff --git a/mregion.h b/mregion.h
@@ -0,0 +1,73 @@
+#ifndef mregion_h_INCLUDED
+#define mregion_h_INCLUDED
+#include "util.h"
+#include <assert.h>
+#include <libical/ical.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef void (*free_func_t)(void *);
+
+typedef struct memory_block {
+ void *ptr;
+ free_func_t free_func;
+ struct memory_block *next;
+} memory_block;
+
+typedef struct {
+ memory_block *head;
+} memory_region;
+
+memory_region *
+create_region(void);
+
+void
+region_register(memory_region *region, void *ptr, free_func_t free_func);
+
+void *
+rmalloc(memory_region *region, size_t size);
+
+void
+rfree_all(memory_region *region);
+
+char *
+rstrins(memory_region *region, const char *str, size_t offset, const char *buf,
+ size_t size);
+// String functions
+char *
+rstrdup(memory_region *region, const char *str);
+
+int
+rasprintf(memory_region *region, char **strp, const char *fmt, ...);
+
+void
+rappend(char **dest, const char *fmt, ...);
+
+// iCal functions
+
+icalparser *
+ricalparser_new(memory_region *region);
+
+void
+ricalcomponent_free(icalcomponent *ic);
+
+icalcomponent *
+ricalcomponent_empty(memory_region *region);
+
+icalcomponent *
+ricalcomponent_new_clone(memory_region *region, icalcomponent *ic);
+
+icalcomponent *
+ricalcomponent_new_from_string(memory_region *region, const char *ic);
+
+char *
+ricalcomponent_as_ical_string(memory_region *region, icalcomponent *ic);
+
+char *
+ricalcomponent_as_ical_string_r(memory_region *region, icalcomponent *ic);
+
+icalcomponent *
+ricalcomponent_new_vcalendar(memory_region *region);
+#endif // mrgeion_h_INCLUDED
diff --git a/path.c b/path.c
@@ -1,5 +1,5 @@
#include "path.h"
-#include "util.h"
+#include "mregion.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
@@ -7,33 +7,26 @@
#include <string.h>
path *
-without_file_extension(const path *p)
+without_file_extension(memory_region *m, const path *p)
{
- char *cpy = xstrdup(p);
+ char *cpy = rstrdup(m, p);
char *last_dot = strrchr(cpy, '.');
char *last_slash = strrchr(cpy, '/');
- // Only remove the extension if dot is after last slash
+ // Only remove the extension if dot is after
if (last_dot && (!last_slash || last_dot > last_slash)) {
*last_dot = '\0';
}
-
return cpy;
}
-void
-free_segments(char **segments, size_t count)
-{
- for (size_t i = 0; i < count; ++i)
- free(segments[i]);
-}
-
path *
-append_path(const path *parentp, const char *childp)
+append_path(memory_region *m, const path *parentp, const char *childp)
{
char *filepath = NULL;
size_t res = asprintf(&filepath, "%s/%s", parentp, childp);
assert(res != -1);
+ region_register(m, filepath, free);
return filepath;
}
@@ -46,35 +39,29 @@ get_filename(const char *full_path)
return base_name + 1;
}
-size_t
-get_parent_path(const char *path, char **buffer)
+char *
+get_parent_path(memory_region *mreg, const char *path)
{
- char *path_copy = strdup(path);
+ char *path_copy = rstrdup(mreg, path);
char *last_slash = strrchr(path_copy, '/');
if (last_slash == NULL) {
- free(path_copy);
- return 0;
+ return NULL;
}
// 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;
+ return "/";
}
- *last_slash = '\0';
- int result = asprintf(buffer, "%s", path_copy);
- assert(result != -1);
- free(path_copy);
- return result;
+ *last_slash = '\0';
+ return path_copy;
}
size_t
-split_path(const path *p, char **segments)
+split_path(memory_region *m, const path *p, char **segments)
{
- char *path_copy = xstrdup(p);
+ char *path_copy = rstrdup(m, p);
size_t count = 0;
char *segment;
@@ -82,11 +69,9 @@ split_path(const path *p, char **segments)
char *saveptr;
segment = strtok_r(path_copy, "/", &saveptr);
while (segment) {
- segments[count++] = xstrdup(segment);
+ segments[count++] = rstrdup(m, segment);
segment = strtok_r(NULL, "/", &saveptr);
}
- free(path_copy);
-
return count;
}
diff --git a/path.h b/path.h
@@ -1,6 +1,9 @@
#ifndef path_h_INCLUDED
#define path_h_INCLUDED
+#include "mregion.h"
#include "util.h"
+#include <assert.h>
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -8,21 +11,21 @@
typedef char path;
path *
-without_file_extension(const path *);
-
-void
-free_segments(path **, size_t);
+without_file_extension(memory_region *m, const path *p);
path *
-append_path(const path *, const char *);
+append_path(memory_region *m, const path *parentp, const char *childp);
const path *
-get_filename(const path *);
+get_filename(const char *full_path);
+
+char *
+get_parent_path(memory_region *mreg, const char *path);
size_t
-get_parent_path(const char *, char **);
-size_t
-split_path(const path *, char **);
+split_path(memory_region *m, const path *p, char **segments);
+
size_t
-write_to_file(const char *filepath, const char *content);
+write_to_file(const path *filepath, const char *content);
+
#endif // path_h_INCLUDED
diff --git a/tree.c b/tree.c
@@ -1,4 +1,5 @@
#include "tree.h"
+#include "mregion.h"
#include "util.h"
#include <stdbool.h>
#include <stdio.h>
@@ -20,6 +21,22 @@ create_tree_node(void *data, void (*free_fn)(void *))
return node;
}
+struct tree_node *
+rcreate_tree_node(void *data, memory_region *m)
+{
+ struct tree_node *node = rmalloc(m, sizeof(struct 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 = NULL;
+
+ return node;
+}
+
size_t
add_child(struct tree_node *parent, struct tree_node *child)
{
@@ -97,8 +114,6 @@ detach_tree_node(struct tree_node *node)
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) {
diff --git a/tree.h b/tree.h
@@ -1,6 +1,7 @@
#ifndef tree_h
#define tree_h
+#include "mregion.h"
#include <stdbool.h>
#include <stdlib.h>
@@ -18,6 +19,9 @@ struct tree_node {
struct tree_node *
create_tree_node(void *data, void (*free_fn)(void *));
+struct tree_node *
+rcreate_tree_node(void *data, memory_region *m);
+
bool
node_has_children(const struct tree_node *node);
diff --git a/util.c b/util.c
@@ -1,5 +1,6 @@
// Copyright © 2019-2024 Michael Forney
// util.c from cproc
+#include "util.h"
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
@@ -15,8 +16,10 @@ xmalloc(size_t len)
void *buf;
buf = malloc(len);
- if (!buf && len)
+ if (!buf && len) {
+ LOG("FAILED TO MALLOC FOR %zu", len);
exit(1);
+ }
return buf;
}