agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit d0566a84d00d523038098fa603e60259f4fa3eee
parent 5fbe24b3c74068345e4a69342db653b5bdda759a
Author: Marc Coquand <marc@coquand.email>
Date:   Sun, 15 Jun 2025 11:23:53 +0100

Refactor out journal_entry (1)

Diffstat:
MMakefile | 8++++----
Ajournal_entry.c | 584+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ajournal_entry.h | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmain.c | 679++-----------------------------------------------------------------------------
Mutil.h | 7+++++++
5 files changed, 730 insertions(+), 673 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,11 +5,11 @@ LIBS = `pkg-config uuid fuse3 libical --cflags --libs`
 
 all: caldavfs
 
-caldavfs: main.c util.c hashmap.c
-	$(CC) $(CFLAGS) main.c util.c hashmap.c -o mount_caldavfs $(LIBS)
+caldavfs: main.c util.c hashmap.c journal_entry.c
+	$(CC) $(CFLAGS) main.c journal_entry.c util.c hashmap.c -o mount_caldavfs $(LIBS)
 
-debug: main.c util.c hashmap.c
-	$(CC) $(CFLAGS_DEBUG) $(DEBUG_FLAGS) main.c util.c hashmap.c -o mount_caldavfs-debug $(LIBS)
+debug: main.c util.c hashmap.c journal_entry.c
+	$(CC) $(CFLAGS_DEBUG) $(DEBUG_FLAGS) main.c journal_entry.c util.c hashmap.c -o mount_caldavfs-debug $(LIBS)
 
 clean:
 	rm -f caldavfs caldavfs-debug
diff --git a/journal_entry.c b/journal_entry.c
@@ -0,0 +1,584 @@
+#include "journal_entry.h"
+
+#include "hashmap.h"
+#include "util.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>
+
+struct hashmap *entries_fuse = NULL;
+
+char ICS_DIR[256];
+
+// journal_entry
+struct hashmap *entries_original_ics = NULL;
+
+// string -> string
+struct hashmap *entries_today = NULL;
+
+struct hashmap *entries_notes = NULL;
+// Full filepath of original ics file
+char *
+get_original_filepath(const struct journal_entry *entry)
+{
+	char *filepath = NULL;
+	asprintf(&filepath, "%s/%s", ICS_DIR, entry->filename_original);
+	return filepath;
+}
+
+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);
+}
+
+char *
+get_filename(const char *full_path)
+{
+	char *base_name = strrchr(full_path, '/');
+	if (!base_name)
+		return (char *)full_path;
+	return base_name + 1;
+}
+
+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 char *prefix = "/entries/";
+	size_t prefix_len = strlen(prefix);
+
+	if (strncmp(path, prefix, prefix_len) != 0) {
+		LOG("Path does not start with '/entries/': '%s'\n", path);
+		return NULL;
+	}
+
+	// Extract the part after "/entries/"
+	const char *key = path + prefix_len;
+
+	// If key is empty (i.e., path is exactly "/entries/"), reject
+	if (strlen(key) == 0) {
+		LOG("Empty key in path: '%s'\n", path);
+		return NULL;
+	}
+
+	LOG("Looking up key: '%s'", key);
+	struct journal_entry *entry = hashmap_get(entries_fuse, key);
+
+	if (!entry) {
+		LOG("No entry found for key: '%s'\n", key);
+		return NULL;
+	}
+
+	LOG("Entry found: filename = '%s', original = '%s', path = '%s'",
+	    entry->filename, entry->filename_original, path);
+	return entry;
+}
+
+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)
+{
+	icalcomponent *component = NULL;
+	FILE *stream = fopen(filename, "r");
+	assert(stream != 0);
+
+	icalparser *parser = icalparser_new();
+	icalparser_set_gen_data(parser, stream);
+
+	char *line;
+	do {
+		line = icalparser_get_line(parser, read_stream);
+		icalcomponent *temp = icalparser_add_line(parser, line);
+		if (temp != NULL && component == NULL) {
+			component = temp; // Save the first completed component
+		}
+	} while (line != NULL);
+
+	fclose(stream);
+	icalparser_free(parser);
+	return component;
+}
+
+// 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)
+{
+	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);
+	if (journal == NULL) {
+		LOG("No journal component");
+		return -2;
+	}
+	entry->component = component;
+	return 0;
+}
+
+char *
+time_string(icaltimetype t, const char *format)
+{
+
+	char *buffer = xmalloc(64);
+	time_t tme = icaltime_as_timet(t);
+	struct tm tm_info;
+	localtime_r(&tme, &tm_info);
+
+	strftime(buffer, 64, format, &tm_info);
+	return buffer;
+}
+
+// Returns -1 on failure, 0 on success
+int
+load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry)
+{
+	char *filepath = NULL;
+	if (asprintf(&filepath, "%s/%s", ICS_DIR, filename) == -1) {
+		perror("aspintf failed");
+		return -1;
+	}
+	entry->filename_original = strdup(filename);
+	if (!entry->filename_original) {
+		perror("strdup failed");
+		free(filepath);
+		return -1;
+	}
+
+	if (parse_ics_to_journal_entry_component(filepath, entry) != 0) {
+		LOG("Failed to parse entry: %s", filename);
+		free(filepath);
+		return -1;
+	}
+
+	// Make sure filename, which happens when
+	// Two entries are created on the same minute
+	// TODO: Allow custom fileformats
+	if (asprintf(&entry->filename, "%s.txt",
+		     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;
+}
+
+// Initializes
+// entries_original_key
+// entries_fuse_key
+// Should only be run once
+void
+load_journal_entries()
+{
+	LOG("Allocating journal entries");
+	if (entries_original_ics) {
+		hashmap_free(entries_original_ics);
+		entries_original_ics = NULL;
+	}
+	if (entries_fuse) {
+		hashmap_free(entries_fuse);
+		entries_fuse = NULL;
+	}
+	entries_original_ics = hashmap_new(NULL);
+	entries_fuse = hashmap_new(free_journal_entry);
+
+	LOG("Loading journal entries from: %s\n", ICS_DIR);
+
+	DIR *dir = opendir(ICS_DIR);
+	if (!dir) {
+		perror("opendir");
+		return;
+	}
+
+	struct dirent *entry;
+	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;
+			}
+
+			if (hashmap_insert(entries_fuse, new_entry->filename,
+					   new_entry) != 0 ||
+			    hashmap_insert(entries_original_ics,
+					   new_entry->filename_original,
+					   new_entry) != 0) {
+				LOG("Failed to insert entry");
+				free_journal_entry(new_entry);
+				continue;
+			}
+		}
+	}
+	closedir(dir);
+}
+
+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));
+	if (size == -1) {
+		LOG("Out of memory");
+		exit(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));
+	if (size == -1) {
+		LOG("Out of mem");
+		exit(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);
+	}
+}
+
+// TODO: Probably better to calculate this on demand
+int
+build_today_hashmap()
+{
+	if (entries_today != NULL)
+		hashmap_free(entries_today);
+	entries_today = hashmap_new(free);
+
+	size_t n_keys = 0;
+	char **keys = hashmap_get_keys(entries_fuse, &n_keys);
+
+	if (!keys) {
+		LOG("Could not get keys");
+		return -1;
+	}
+
+	for (size_t i = 0; i < n_keys; i++) {
+		struct journal_entry *entry =
+		    hashmap_get(entries_fuse, keys[i]);
+		if (!entry)
+			continue;
+
+		// Get the dtstamp of the first icalcomponent
+		icalcomponent *component = entry->component;
+		if (!component)
+			continue;
+
+		struct icaltimetype dt = icalcomponent_get_dtstamp(component);
+
+		if (icaltime_compare_date_only(dt, icaltime_today()) == 0) {
+			const char *summary =
+			    icalcomponent_get_summary(component);
+			if (summary == NULL) {
+				LOG("SUMMARY IS NULL");
+				continue;
+			}
+			char *filename;
+
+			asprintf(&filename, "%s", summary);
+
+			if (filename == NULL) {
+				LOG("Could not get filename");
+				continue;
+			}
+
+			char *dest;
+
+			if (asprintf(&dest, "../entries/%s", entry->filename) ==
+			    -1)
+				return -1;
+
+			hashmap_insert(entries_today, filename, dest);
+		}
+	}
+	LOG("Build complete");
+
+	hashmap_free_keys(keys, n_keys);
+	return 0;
+}
+
+int
+build_notes_hashmap()
+{
+	if (entries_notes != NULL)
+		hashmap_free(entries_notes);
+	entries_notes = hashmap_new(free);
+	LOG("BUILDING ENTRIES");
+
+	size_t n_keys = 0;
+	char **keys = hashmap_get_keys(entries_fuse, &n_keys);
+
+	if (!keys) {
+		LOG("Could not get keys");
+		return -1;
+	}
+	LOG("GOT ENTRIES");
+
+	for (size_t i = 0; i < n_keys; i++) {
+		struct journal_entry *entry =
+		    hashmap_get(entries_fuse, keys[i]);
+
+		LOG("Looking up entry");
+		if (!entry)
+			continue;
+
+		// Get the dtstamp of the first icalcomponent
+		icalcomponent *component = entry->component;
+		if (!component)
+			continue;
+
+		icalcomponent_kind type =
+		    icalcomponent_isa(icalcomponent_get_inner(component));
+
+		if (type == ICAL_VJOURNAL_COMPONENT) {
+			const char *summary =
+			    icalcomponent_get_summary(component);
+			if (summary == NULL) {
+				LOG("SUMMARY IS NULL");
+				continue;
+			}
+			char *filename;
+			asprintf(&filename, "%s", summary);
+
+			if (filename == NULL) {
+				LOG("Could not get filename");
+				continue;
+			}
+
+			char *dest;
+
+			if (asprintf(&dest, "../entries/%s", entry->filename) ==
+			    -1)
+				return -1;
+
+			hashmap_insert(entries_notes, filename, dest);
+		}
+	}
+	LOG("Build complete");
+
+	hashmap_free_keys(keys, n_keys);
+	return 0;
+}
+
+struct journal_entry *
+copy_journal_entry(const struct journal_entry *src)
+{
+	struct journal_entry *copy = xcalloc(1, sizeof(struct journal_entry));
+
+	copy->filename = strdup(src->filename);
+	copy->filename_original = strdup(src->filename_original);
+	copy->component = icalcomponent_new_clone(src->component);
+
+	return copy;
+}
+
+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);
+
+	LOG("Set last modified");
+
+	char *ical_str = icalcomponent_as_ical_string_r(entry->component);
+
+	LOG("Got ical_str %s", ical_str);
+
+	FILE *f = fopen(filepath, "w");
+	if (!f) {
+		perror("Failed to open ICS file");
+		free(ical_str);
+		free(filepath);
+		return -EIO;
+	}
+
+	fputs(ical_str, f);
+	fclose(f);
+	free(ical_str);
+	free(filepath);
+
+	return 0;
+}
+size_t
+load_caldavfs_environment()
+{
+	char *ics_dir_env = getenv("CALDAVFS_ICS_DIR");
+
+	if (ics_dir_env == NULL) {
+		fprintf(
+		    stderr,
+		    "Must set CALDAVFS_ICS_DIR to the absolute path of your "
+		    "calendar. See README.\n");
+		return -1;
+	}
+
+	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));
+		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 = strdup(new);
+	if (!new_copy)
+		return -ENOMEM;
+	char *new_filename = get_filename(new_copy);
+
+	char *old_filename = get_filename(old);
+	if (!old_filename)
+		return -EINVAL;
+
+	char *new_summary = strdup(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 journal_entry *old_entry = get_entry_from_fuse_path(old);
+	if (!old_entry) {
+		LOG("Entry not found '%s'", old);
+		free(new_copy);
+		free(new_summary);
+		return -EIO;
+	}
+
+	struct journal_entry *new_entry = copy_journal_entry(old_entry);
+	if (!new_entry) {
+		free(new_copy);
+		free(new_summary);
+		return -EIO;
+	}
+
+	icalcomponent_set_summary(new_entry->component, new_summary);
+	// TODO: Filename must be unique
+	asprintf(&new_entry->filename, "%s", new_filename);
+	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,
+		       new_entry);
+
+	hashmap_insert(entries_fuse, new_entry->filename, new_entry);
+
+	// Insert original ics first to avoid use after free
+	// Insert frees older entry
+	hashmap_remove(entries_fuse, old_entry->filename);
+
+	write_entry_to_ical_file(new_entry);
+	free(new_copy);
+
+	return 0;
+}
+
diff --git a/journal_entry.h b/journal_entry.h
@@ -0,0 +1,125 @@
+#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.txt
+	char *filename;
+	// filename_original is the relative path to ICS_DIR
+	// I.E. 910319208nrao19p.ics
+	char *filename_original;
+	icalcomponent *component;
+};
+
+extern char ICS_DIR[256];
+
+// We create two maps that link to the same entries
+// journal_entry
+extern struct hashmap *entries_fuse;
+
+// journal_entry
+extern struct hashmap *entries_original_ics;
+
+// string -> string
+extern struct hashmap *entries_today;
+
+// string -> string
+extern struct hashmap *entries_notes;
+
+// Full filepath of original ics file
+char *
+get_original_filepath(const struct journal_entry *entry);
+
+void
+free_journal_entry(void *val);
+
+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(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);
+
+// Returns -1 on failure, 0 on success
+int
+load_journal_entry_from_ics_file(char *filename, struct journal_entry *entry);
+
+// 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();
+
+int
+build_notes_hashmap();
+
+size_t
+load_caldavfs_environment();
+
+#endif // JOURNAL_ENTRY_H
diff --git a/main.c b/main.c
@@ -1,6 +1,7 @@
 #define FUSE_USE_VERSION 31
 
 #include "hashmap.h"
+#include "journal_entry.h"
 #include "util.h"
 #include <dirent.h>
 #include <errno.h>
@@ -18,457 +19,11 @@
 #include <unistd.h>
 #include <uuid/uuid.h>
 #include <wordexp.h>
-#define LOG(fmt, ...)                                                          \
-	fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
 
 // Listening to file changes of the original content
 #define EVENT_BUF_LEN (1024 * (sizeof(struct inotify_event) + 16))
 
-char ICS_DIR[256];
-
-const char *date_title_fmt = "%Y%m%d-%H:%M";
-regex_t regex_fuse_file;
-
-time_t INVALID_TIME = (time_t)-1;
-
-icaltimezone *local_ical_time;
-
-// 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.
-typedef struct {
-	// filename is full path relative to fuse directory
-	// I.E. hello world.txt
-	char *filename;
-	// filename_original is full path relative to fuse directory
-	// I.E. 910319208nrao19p.ics
-	char *filename_original;
-	icalcomponent *component;
-} journal_entry;
-
-// We create two maps that link to the same entries
-// journal_entry
-struct hashmap *entries_fuse = NULL;
-
-// journal_entry
-struct hashmap *entries_original_ics = NULL;
-
-// string -> string
-struct hashmap *entries_today = NULL;
-
-// string -> string
-struct hashmap *entries_notes = NULL;
-
 static pthread_mutex_t entries_mutex = PTHREAD_MUTEX_INITIALIZER;
-icaltimetype
-get_ical_now()
-{
-	return icaltime_from_timet_with_zone(time(NULL), 0,
-					     icaltimezone_get_utc_timezone());
-}
-
-// Full filepath of original ics file
-char *
-get_original_filepath(const journal_entry *entry)
-{
-	char *filepath = NULL;
-	asprintf(&filepath, "%s/%s", ICS_DIR, entry->filename_original);
-	return filepath;
-}
-
-void
-free_journal_entry(void *val)
-{
-	journal_entry *entry = (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);
-}
-
-char *
-get_filename(const char *full_path)
-{
-	char *base_name = strrchr(full_path, '/');
-	if (!base_name)
-		return (char *)full_path;
-	return base_name + 1;
-}
-int
-compile_regexes(regex_t *regex_fuse_file)
-{
-	if (regcomp(regex_fuse_file,
-		    "^([0-9]{8})-([0-9]{2}:[0-9]{2})(_[^/\\:*?\"<>|]+)?\\.txt$",
-		    REG_EXTENDED) != 0)
-		return -1;
-	return 0;
-}
-
-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 char *prefix = "/entries/";
-	size_t prefix_len = strlen(prefix);
-
-	if (strncmp(path, prefix, prefix_len) != 0) {
-		LOG("Path does not start with '/entries/': '%s'\n", path);
-		return NULL;
-	}
-
-	// Extract the part after "/entries/"
-	const char *key = path + prefix_len;
-
-	// If key is empty (i.e., path is exactly "/entries/"), reject
-	if (strlen(key) == 0) {
-		LOG("Empty key in path: '%s'\n", path);
-		return NULL;
-	}
-
-	LOG("Looking up key: '%s'", key);
-	journal_entry *entry = hashmap_get(entries_fuse, key);
-
-	if (!entry) {
-		LOG("No entry found for key: '%s'\n", key);
-		return NULL;
-	}
-
-	LOG("Entry found: filename = '%s', original = '%s', path = '%s'",
-	    entry->filename, entry->filename_original, path);
-	return entry;
-}
-
-char *
-read_stream(char *s, size_t size, void *d)
-{
-	return fgets(s, (int)size, (FILE *)d);
-}
-
-icalcomponent *
-parse_ics_file(const char *filename)
-{
-	icalcomponent *component = NULL;
-	FILE *stream = fopen(filename, "r");
-	assert(stream != 0);
-
-	icalparser *parser = icalparser_new();
-	icalparser_set_gen_data(parser, stream);
-
-	char *line;
-	do {
-		line = icalparser_get_line(parser, read_stream);
-		icalcomponent *temp = icalparser_add_line(parser, line);
-		if (temp != NULL && component == NULL) {
-			component = temp; // Save the first completed component
-		}
-	} while (line != NULL);
-
-	fclose(stream);
-	icalparser_free(parser);
-	return component;
-}
-
-// We assume that there is only one entry in the file
-int
-parse_ics_to_journal_entry_component(const char *filename, 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);
-	if (journal == NULL) {
-		LOG("No journal component");
-		return -2;
-	}
-	entry->component = component;
-	return 0;
-}
-
-char *
-time_string(icaltimetype t, const char *format)
-{
-
-	char *buffer = xmalloc(64);
-	time_t tme = icaltime_as_timet(t);
-	struct tm tm_info;
-	localtime_r(&tme, &tm_info);
-
-	strftime(buffer, 64, format, &tm_info);
-	return buffer;
-}
-
-// Returns -1 on failure, 0 on success
-int
-load_journal_entry_from_ics_file(char *filename, journal_entry *entry)
-{
-	char *filepath = NULL;
-	if (asprintf(&filepath, "%s/%s", ICS_DIR, filename) == -1) {
-		perror("aspintf failed");
-		return -1;
-	}
-	entry->filename_original = strdup(filename);
-	if (!entry->filename_original) {
-		perror("strdup failed");
-		free(filepath);
-		return -1;
-	}
-
-	if (parse_ics_to_journal_entry_component(filepath, entry) != 0) {
-		LOG("Failed to parse entry: %s", filename);
-		free(filepath);
-		return -1;
-	}
-
-	// Make sure filename, which happens when
-	// Two entries are created on the same minute
-	// TODO: Allow custom fileformats
-	if (asprintf(&entry->filename, "%s.txt",
-		     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;
-}
-
-// Initializes
-// entries_original_key
-// entries_fuse_key
-// Should only be run once
-void
-load_journal_entries()
-{
-	pthread_mutex_lock(&entries_mutex);
-	LOG("Allocating journal entries");
-	if (entries_original_ics) {
-		hashmap_free(entries_original_ics);
-		entries_original_ics = NULL;
-	}
-	if (entries_fuse) {
-		hashmap_free(entries_fuse);
-		entries_fuse = NULL;
-	}
-	entries_original_ics = hashmap_new(NULL);
-	entries_fuse = hashmap_new(free_journal_entry);
-
-	LOG("Loading journal entries from: %s\n", ICS_DIR);
-
-	DIR *dir = opendir(ICS_DIR);
-	if (!dir) {
-		perror("opendir");
-		pthread_mutex_unlock(&entries_mutex);
-		return;
-	}
-
-	struct dirent *entry;
-	while ((entry = readdir(dir))) {
-		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
-			journal_entry *new_entry =
-			    xcalloc(1, sizeof(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;
-			}
-
-			if (hashmap_insert(entries_fuse, new_entry->filename,
-					   new_entry) != 0 ||
-			    hashmap_insert(entries_original_ics,
-					   new_entry->filename_original,
-					   new_entry) != 0) {
-				LOG("Failed to insert entry");
-				free_journal_entry(new_entry);
-				continue;
-			}
-		}
-	}
-	closedir(dir);
-	pthread_mutex_unlock(&entries_mutex);
-}
-
-size_t
-write_entry_content(char *buf, journal_entry *entry)
-{
-	// Allocate memory for the content
-	size_t size = asprintf(&buf, "%s",
-			       icalcomponent_get_description(entry->component));
-	if (size == -1) {
-		LOG("Out of memory");
-		exit(1);
-	}
-	return size;
-}
-
-size_t
-get_entry_content_size(journal_entry *entry)
-{
-	size_t size = snprintf(NULL, 0, "%s",
-			       icalcomponent_get_description(entry->component));
-	if (size == -1) {
-		LOG("Out of mem");
-		exit(1);
-	}
-	return size;
-}
-
-// Gets last modified, and if not exists, falls back to dtstamp
-icaltimetype
-get_last_modified(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);
-	}
-}
-
-// TODO: Probably better to calculate this on demand
-int
-build_today_hashmap()
-{
-	if (entries_today != NULL)
-		hashmap_free(entries_today);
-	entries_today = hashmap_new(free);
-
-	size_t n_keys = 0;
-	char **keys = hashmap_get_keys(entries_fuse, &n_keys);
-
-	if (!keys) {
-		LOG("Could not get keys");
-		return -ENOMEM;
-	}
-
-	for (size_t i = 0; i < n_keys; i++) {
-		journal_entry *entry = hashmap_get(entries_fuse, keys[i]);
-		if (!entry)
-			continue;
-
-		// Get the dtstamp of the first icalcomponent
-		icalcomponent *component = entry->component;
-		if (!component)
-			continue;
-
-		struct icaltimetype dt = icalcomponent_get_dtstamp(component);
-
-		if (icaltime_compare_date_only(dt, icaltime_today()) == 0) {
-			const char *summary =
-			    icalcomponent_get_summary(component);
-			if (summary == NULL) {
-				LOG("SUMMARY IS NULL");
-				continue;
-			}
-			char *filename;
-
-			asprintf(&filename, "%s", summary);
-
-			if (filename == NULL) {
-				LOG("Could not get filename");
-				continue;
-			}
-
-			char *dest;
-
-			if (asprintf(&dest, "../entries/%s", entry->filename) ==
-			    -1)
-				return -ENOMEM;
-
-			hashmap_insert(entries_today, filename, dest);
-		}
-	}
-	LOG("Build complete");
-
-	hashmap_free_keys(keys, n_keys);
-	return 0;
-}
-
-int
-build_notes_hashmap()
-{
-	if (entries_notes != NULL)
-		hashmap_free(entries_notes);
-	entries_notes = hashmap_new(free);
-	LOG("BUILDING ENTRIES");
-
-	size_t n_keys = 0;
-	char **keys = hashmap_get_keys(entries_fuse, &n_keys);
-
-	if (!keys) {
-		LOG("Could not get keys");
-		return -ENOMEM;
-	}
-	LOG("GOT ENTRIES");
-
-	for (size_t i = 0; i < n_keys; i++) {
-		journal_entry *entry = hashmap_get(entries_fuse, keys[i]);
-
-		LOG("Looking up entry");
-		if (!entry)
-			continue;
-
-		// Get the dtstamp of the first icalcomponent
-		icalcomponent *component = entry->component;
-		if (!component)
-			continue;
-
-		icalcomponent_kind type =
-		    icalcomponent_isa(icalcomponent_get_inner(component));
-
-		if (type == ICAL_VJOURNAL_COMPONENT) {
-			const char *summary =
-			    icalcomponent_get_summary(component);
-			if (summary == NULL) {
-				LOG("SUMMARY IS NULL");
-				continue;
-			}
-			char *filename;
-			asprintf(&filename, "%s", summary);
-
-			if (filename == NULL) {
-				LOG("Could not get filename");
-				continue;
-			}
-
-			char *dest;
-
-			if (asprintf(&dest, "../entries/%s", entry->filename) ==
-			    -1)
-				return -ENOMEM;
-
-			hashmap_insert(entries_notes, filename, dest);
-		}
-	}
-	LOG("Build complete");
-
-	hashmap_free_keys(keys, n_keys);
-	return 0;
-}
 
 int
 journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
@@ -504,7 +59,6 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 	const char *today_prefix = "/today/";
 	if (strncmp(path, today_prefix, strlen(today_prefix)) == 0) {
 		const char *key = path + strlen(today_prefix);
-		build_today_hashmap();
 		char *entry_filename = hashmap_get(entries_today, key);
 		if (!entry_filename) {
 			ret_code = -ENOENT;
@@ -519,9 +73,6 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 	const char *notes_prefix = "/notes/";
 	if (strncmp(path, notes_prefix, strlen(notes_prefix)) == 0) {
 		const char *key = path + strlen(notes_prefix);
-		LOG("NOTES");
-		build_notes_hashmap();
-		LOG("BUILT NOTES HASHMAP");
 		char *entry_filename = hashmap_get(entries_notes, key);
 		if (!entry_filename) {
 			ret_code = -ENOENT;
@@ -533,7 +84,7 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 		goto unlock_and_return;
 	}
 
-	journal_entry *entry = get_entry_from_fuse_path(path);
+	struct journal_entry *entry = get_entry_from_fuse_path(path);
 	if (!entry) {
 		LOG("Entry not found");
 		ret_code = -ENOENT;
@@ -686,7 +237,7 @@ journal_open(const char *path, struct fuse_file_info *fi)
 {
 	pthread_mutex_lock(&entries_mutex);
 	LOG("Opening journal for %s", path);
-	journal_entry *entry = get_entry_from_fuse_path(path);
+	struct journal_entry *entry = get_entry_from_fuse_path(path);
 	pthread_mutex_unlock(&entries_mutex);
 	return entry ? 0 : -ENOENT;
 }
@@ -699,7 +250,7 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
 	pthread_mutex_lock(&entries_mutex);
 	LOG("READ on path %s", path);
 
-	journal_entry *entry = get_entry_from_fuse_path(path);
+	struct journal_entry *entry = get_entry_from_fuse_path(path);
 	if (!entry) {
 		ret_code = -ENOENT;
 		goto unlock_and_return;
@@ -744,83 +295,12 @@ unlock_and_return:
 	return ret_code;
 }
 
-size_t
-parse_entry_content(journal_entry *entry, const char *buf, size_t size)
-{
-	char *copy = strndup(buf, size);
-
-	char *description = NULL;
-
-	char *sep = strstr(copy, "\n---\n");
-
-	// Malformed input
-	if (!sep) {
-		free(copy);
-		return -EINVAL;
-	}
-
-	*sep = '\0';
-	description = sep + 5;
-
-	char *summary = strtok(copy, "\n");
-	if (!summary) {
-		free(copy);
-		return -EINVAL;
-	}
-
-	icalcomponent_set_summary(entry->component, summary);
-	icalcomponent_set_description(entry->component, description);
-	free(copy);
-	return 0;
-}
-
-size_t
-write_entry_to_ical_file(const 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);
-
-	LOG("Set last modified");
-
-	char *ical_str = icalcomponent_as_ical_string_r(entry->component);
-
-	LOG("Got ical_str %s", ical_str);
-
-	FILE *f = fopen(filepath, "w");
-	if (!f) {
-		perror("Failed to open ICS file");
-		free(ical_str);
-		free(filepath);
-		return -EIO;
-	}
-
-	fputs(ical_str, f);
-	fclose(f);
-	free(ical_str);
-	free(filepath);
-
-	return 0;
-}
-
 int
 journal_write(const char *path, const char *buf, size_t size, off_t offset,
 	      struct fuse_file_info *fi)
 {
 	pthread_mutex_lock(&entries_mutex);
-	journal_entry *entry = get_entry_from_fuse_path(path);
+	struct journal_entry *entry = get_entry_from_fuse_path(path);
 	if (!entry) {
 		pthread_mutex_unlock(&entries_mutex);
 		return -ENOENT;
@@ -841,106 +321,6 @@ journal_write(const char *path, const char *buf, size_t size, off_t offset,
 	return size;
 }
 
-journal_entry *
-copy_journal_entry(const journal_entry *src)
-{
-	journal_entry *copy = xcalloc(1, sizeof(journal_entry));
-
-	copy->filename = strdup(src->filename);
-	copy->filename_original = strdup(src->filename_original);
-	copy->component = icalcomponent_new_clone(src->component);
-
-	return copy;
-}
-
-time_t
-timestamp_from_filename(const char *filename)
-{
-	regmatch_t matches[3];
-
-	if (regexec(&regex_fuse_file, filename, 3, matches, 0) != 0)
-		return INVALID_TIME;
-
-	int year, month, day, hour, minute;
-
-	if (sscanf(filename + matches[1].rm_so, "%4d%2d%2d", &year, &month,
-		   &day) != 3)
-		return INVALID_TIME;
-
-	if (sscanf(filename + matches[2].rm_so, "%2d:%2d", &hour, &minute) != 2)
-		return INVALID_TIME;
-
-	struct tm tm_time = {.tm_year = year - 1900,
-			     .tm_mon = month - 1,
-			     .tm_mday = day,
-			     .tm_hour = hour,
-			     .tm_min = minute,
-			     .tm_sec = 0,
-			     .tm_isdst = -1};
-
-	return mktime(&tm_time);
-}
-
-int
-do_journal_entry_rename(const char *old, const char *new)
-{
-	char *new_copy = strdup(new);
-	if (!new_copy)
-		return -ENOMEM;
-	char *new_filename = get_filename(new_copy);
-
-	char *old_filename = get_filename(old);
-	if (!old_filename)
-		return -EINVAL;
-
-	char *new_summary = strdup(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);
-
-	journal_entry *old_entry = get_entry_from_fuse_path(old);
-	if (!old_entry) {
-		LOG("Entry not found '%s'", old);
-		free(new_copy);
-		free(new_summary);
-		return -EIO;
-	}
-
-	journal_entry *new_entry = copy_journal_entry(old_entry);
-	if (!new_entry) {
-		free(new_copy);
-		free(new_summary);
-		return -EIO;
-	}
-
-	icalcomponent_set_summary(new_entry->component, new_summary);
-	// TODO: Filename must be unique
-	asprintf(&new_entry->filename, "%s", new_filename);
-	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,
-		       new_entry);
-
-	hashmap_insert(entries_fuse, new_entry->filename, new_entry);
-
-	// Insert original ics first to avoid use after free
-	// Insert frees older entry
-	hashmap_remove(entries_fuse, old_entry->filename);
-
-	write_entry_to_ical_file(new_entry);
-	free(new_copy);
-
-	return 0;
-}
-
 // Aka remove or delete
 int
 journal_unlink(const char *file)
@@ -953,7 +333,7 @@ journal_unlink(const char *file)
 	const char *keyname = strrchr(file, '/');
 	keyname++;
 
-	journal_entry *entry = hashmap_get(entries_fuse, keyname);
+	struct journal_entry *entry = hashmap_get(entries_fuse, keyname);
 	if (!entry) {
 		LOG("Entry does not exist");
 		pthread_mutex_unlock(&entries_mutex);
@@ -1075,7 +455,8 @@ journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
 	char *without_extension = strdup(new_basename);
 	remove_file_extension(without_extension);
 
-	journal_entry *new_entry = xcalloc(1, sizeof(journal_entry));
+	struct journal_entry *new_entry =
+	    xcalloc(1, sizeof(struct journal_entry));
 	icalcomponent *vjournal_component =
 	    create_vjournal_entry(without_extension);
 
@@ -1115,7 +496,8 @@ update_or_create_fuse_entry_from_original(const char *filename)
 {
 	pthread_mutex_lock(&entries_mutex);
 	char *base_name = strrchr(filename, '/');
-	journal_entry *new_entry = xcalloc(1, sizeof(journal_entry));
+	struct journal_entry *new_entry =
+	    xcalloc(1, sizeof(struct journal_entry));
 
 	if (load_journal_entry_from_ics_file(base_name, new_entry) != 0) {
 		// Parsing failed, clean up and maybe remove old entry
@@ -1239,47 +621,12 @@ static const struct fuse_operations journal_oper = {.getattr = journal_getattr,
 							journal_readlink,
 						    .rename = journal_rename};
 
-size_t
-load_caldavfs_environment()
-{
-	char *ics_dir_env = getenv("CALDAVFS_ICS_DIR");
-
-	if (ics_dir_env == NULL) {
-		fprintf(
-		    stderr,
-		    "Must set CALDAVFS_ICS_DIR to the absolute path of your "
-		    "calendar. See README.\n");
-		return -1;
-	}
-
-	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));
-		return 0;
-	}
-
-	fprintf(stderr, "CALDAVFS_ICS_DIR is not a directory: %s\n",
-		expanded_path);
-	return -1;
-}
-
 int
 main(int argc, char *argv[])
 {
 	pthread_t watcher_thread;
 
 	tzset();
-	const char *tzid = getenv("TZ");
-	local_ical_time = icaltimezone_get_builtin_timezone(tzid);
 
 	if (load_caldavfs_environment() != 0) {
 		fprintf(stderr, "Failed to load ICS directory\n");
@@ -1291,11 +638,6 @@ main(int argc, char *argv[])
 		return 1;
 	}
 
-	if (compile_regexes(&regex_fuse_file) != 0) {
-		fprintf(stderr, "Failed to compile regular expressions\n");
-		return -1;
-	}
-
 	load_journal_entries();
 	int ret = fuse_main(argc, argv, &journal_oper, NULL);
 	LOG("Cleaning up");
@@ -1310,7 +652,6 @@ main(int argc, char *argv[])
 	hashmap_free(entries_notes);
 	LOG("Hashmap freed");
 
-	regfree(&regex_fuse_file);
 	LOG("Regexp freed");
 	exit(ret);
 }
diff --git a/util.h b/util.h
@@ -1,5 +1,11 @@
+#ifndef UTIL_H
+#define UTIL_H
+
 #include <stdlib.h>
 
+#define LOG(fmt, ...)                                                          \
+	fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
+
 void *
 reallocarray(void *, size_t, size_t);
 void *
@@ -9,3 +15,4 @@ xmalloc(size_t);
 void *
 xcalloc(size_t, size_t);
 
+#endif