agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit ee300309184acf712d973b6cd6e34dbb4a4007db
parent c39eefc45b224482cb68eb52f83fe397d5cf247d
Author: Marc Coquand <marc@coquand.email>
Date:   Tue,  3 Jun 2025 15:02:18 +0100

Refactors

Diffstat:
Mmain.c | 235++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 142 insertions(+), 93 deletions(-)
diff --git a/main.c b/main.c
@@ -53,6 +53,15 @@ get_ical_now()
 					     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)
 {
@@ -94,8 +103,14 @@ get_entry_from_fuse_path(const char *path)
 
 	LOG("looking up key '%s'\n", key);
 	journal_entry *entry = hashmap_get(entries_fuse_key, key);
-	if (!entry)
+
+	if (!entry) {
 		LOG("no entry found for key '%s'\n", key);
+		return NULL;
+	}
+
+	LOG("Entry is %s", entry->filename);
+	LOG("Entry original is %s", entry->filename_original);
 	return entry;
 }
 
@@ -199,6 +214,47 @@ get_unique_filename(journal_entry *entry, struct hashmap *entries_map)
 	return unique_filename;
 }
 
+// Returns -1 on failure, 0 on success
+int
+load_journal_entry(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(filepath, entry) != 0) {
+		LOG("Failed to parse entry: %s", filename);
+		free(filepath);
+		return -1;
+	}
+
+	LOG("Parsed %s", entry->filename_original);
+
+	// Make sure filename, which happens when
+	// Two entries are created on the same minute
+	char *unique_filename = get_unique_filename(entry, entries_fuse_key);
+	if (!unique_filename) {
+		LOG("Failed to generate filename for %s", filename);
+
+		free(filepath);
+		return -1;
+	}
+
+	entry->filename = unique_filename;
+	LOG("Got unique filename %s", unique_filename);
+
+	free(filepath);
+	return 0;
+}
+
 // Initializes
 // entries_original_key
 // entries_fuse_key
@@ -221,65 +277,27 @@ load_journal_entries()
 	}
 
 	struct dirent *entry;
-	char *filepath;
-
 	while ((entry = readdir(dir))) {
 		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
-			if (asprintf(&filepath, "%s/%s", ICS_DIR,
-				     entry->d_name) == -1) {
-				perror("aspintf failed");
-				continue;
-			}
-
 			journal_entry *new_entry =
 			    xcalloc(1, sizeof(journal_entry));
 
-			new_entry->filename_original = strdup(entry->d_name);
-			if (!new_entry->filename_original) {
-				perror("strdup failed");
-
-				free(filepath);
-				free_journal_entry(new_entry);
-				continue;
-			}
-
-			if (parse_ics_to_journal_entry(filepath, new_entry) !=
-			    0) {
-				LOG("Failed to parse entry: %s", entry->d_name);
-
+			if (load_journal_entry(entry->d_name, new_entry) != 0) {
+				LOG("FAILED TO LOAD JOURNAL ENTRY");
 				free_journal_entry(new_entry);
 				continue;
 			}
 
-			LOG("Parsed %s", new_entry->filename_original);
-
-			// Make sure filename, which happens when
-			// Two entries are created on the same minute
-			char *unique_filename =
-			    get_unique_filename(new_entry, entries_fuse_key);
-			if (!unique_filename) {
-				LOG("Failed to generate filename for %s",
-				    entry->d_name);
-
-				free_journal_entry(new_entry);
-				continue;
-			}
-
-			new_entry->filename = unique_filename;
-
-			if (hashmap_insert(entries_fuse_key, unique_filename,
+			if (hashmap_insert(entries_fuse_key,
+					   new_entry->filename,
 					   new_entry) != 0 ||
 			    hashmap_insert(entries_original_key,
 					   new_entry->filename_original,
 					   new_entry) != 0) {
 				LOG("Failed to insert entry");
-
-				free(filepath);
 				free_journal_entry(new_entry);
 				continue;
 			}
-
-			free(filepath);
 		}
 	}
 	closedir(dir);
@@ -334,6 +352,7 @@ int
 journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 {
 	pthread_mutex_lock(&entries_mutex);
+	LOG("STAT %s", path);
 
 	memset(stbuf, 0, sizeof(struct stat));
 
@@ -381,6 +400,8 @@ journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
 {
 
 	pthread_mutex_lock(&entries_mutex);
+	LOG("READDIR %s", path);
+
 	size_t n_keys = 0;
 	char **keys = hashmap_get_keys(entries_fuse_key, &n_keys);
 	if (!keys) {
@@ -404,9 +425,8 @@ 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);
-	int ret = entry ? 0 : -ENOENT;
 	pthread_mutex_unlock(&entries_mutex);
-	return ret;
+	return entry ? 0 : -ENOENT;
 }
 
 int
@@ -493,8 +513,11 @@ parse_entry_content(journal_entry *entry, const char *buf, size_t size)
 }
 
 size_t
-write_entry_to_ical_file(const journal_entry *entry, const char *filepath)
+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);
@@ -506,31 +529,28 @@ write_entry_to_ical_file(const journal_entry *entry, const char *filepath)
 		icalproperty_set_lastmodified(prop, get_ical_now());
 	}
 
+	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;
 }
 
-// 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;
-}
-
 int
 journal_write(const char *path, const char *buf, size_t size, off_t offset,
 	      struct fuse_file_info *fi)
@@ -549,13 +569,10 @@ journal_write(const char *path, const char *buf, size_t size, off_t offset,
 		return ret;
 	}
 
-	char *filepath = get_original_filepath(entry);
-	if (write_entry_to_ical_file(entry, filepath) != 0) {
-		free(filepath);
+	if (write_entry_to_ical_file(entry) != 0) {
 		pthread_mutex_unlock(&entries_mutex);
 		return -EIO;
 	}
-	free(filepath);
 	pthread_mutex_unlock(&entries_mutex);
 	return size;
 }
@@ -631,15 +648,10 @@ do_journal_entry_rename(time_t new_created_at, const char *old, const char *new)
 		       new_entry);
 	hashmap_insert(entries_fuse_key, new_entry->filename, new_entry);
 
-	// Write back to the original
-	char *filepath = get_original_filepath(new_entry);
-
-	if (write_entry_to_ical_file(new_entry, filepath) != 0) {
-		free(filepath);
+	if (write_entry_to_ical_file(new_entry) != 0) {
 		return -EIO;
 	}
 
-	free(filepath);
 	return 0;
 }
 
@@ -731,7 +743,8 @@ create_new_unique_ics_uid()
 icalcomponent *
 create_vjournal_entry(time_t created_at)
 {
-	struct icaltimetype created_at_ical = get_ical_now();
+	struct icaltimetype created_at_ical = icaltime_from_timet_with_zone(
+	    created_at, 0, icaltimezone_get_utc_timezone());
 
 	icalcomponent *calendar = icalcomponent_new_vcalendar();
 
@@ -744,6 +757,7 @@ create_vjournal_entry(time_t created_at)
 
 	icalcomponent_add_property(
 	    journal, icalproperty_new_uid(create_new_unique_ics_uid()));
+
 	icalcomponent_add_property(journal,
 				   icalproperty_new_dtstamp(created_at_ical));
 	icalcomponent_add_property(journal, icalproperty_new_summary(""));
@@ -770,8 +784,20 @@ journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
 		pthread_mutex_unlock(&entries_mutex);
 		return -EINVAL;
 	}
+
+	LOG("Got created at");
 	journal_entry *new_entry = xcalloc(1, sizeof(journal_entry));
-	create_vjournal_entry(created_at);
+	icalcomponent *vjournal_component = create_vjournal_entry(created_at);
+
+	new_entry->component = vjournal_component;
+	new_entry->filename = strdup(new_basename);
+	if (asprintf(&new_entry->filename_original, "%s.ics",
+		     icalcomponent_get_uid(new_entry->component)) == -1) {
+		LOG("Failed to create journal entry");
+		free_journal_entry(new_entry);
+		return -ENOMEM;
+	}
+	LOG("Inserting vjournal entry");
 
 	hashmap_insert(entries_original_key, new_entry->filename_original,
 		       new_entry);
@@ -779,8 +805,11 @@ journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
 
 	// Write back to the original
 	char *filepath = get_original_filepath(new_entry);
+	LOG("Got original filepath %s. From %s, and %s", filepath,
+	    new_entry->filename_original, new_entry->filename);
 
-	if (write_entry_to_ical_file(new_entry, filepath) != 0) {
+	if (write_entry_to_ical_file(new_entry) != 0) {
+		LOG("Write to entry failed");
 		free(filepath);
 		return -EIO;
 	}
@@ -792,36 +821,22 @@ journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
 }
 
 void
-update_fuse_entry_from_original(const char *filename)
+update_or_create_fuse_entry_from_original(const char *filename)
 {
 	pthread_mutex_lock(&entries_mutex);
-	const char *base_name = strrchr(filename, '/');
-	journal_entry *entry = hashmap_get(entries_original_key, base_name);
-	// TODO: Create entry when file is not found
-	if (!entry) {
-		LOG("Entry not found. Registering new file");
-		pthread_mutex_unlock(&entries_mutex);
-		return;
-	}
+	char *base_name = strrchr(filename, '/');
 	journal_entry *new_entry = xcalloc(1, sizeof(journal_entry));
-	if (!new_entry) {
-		perror("calloc");
-		pthread_mutex_unlock(&entries_mutex);
-		return;
-	}
 
-	LOG("Parsing ICS file %s", filename);
-
-	if (parse_ics_to_journal_entry(filename, new_entry) != 0) {
-		// Parsing failed, clean up and maybe remove old entry if any
+	if (load_journal_entry(base_name, new_entry) != 0) {
+		// Parsing failed, clean up and maybe remove old entry
+		// if any
+		LOG("PARSING FAILED");
 		free_journal_entry(new_entry);
 		pthread_mutex_unlock(&entries_mutex);
 		return;
 	}
-
-	new_entry->filename = strndup(entry->filename, strlen(entry->filename));
-	new_entry->filename_original =
-	    strndup(entry->filename_original, strlen(entry->filename_original));
+	pthread_mutex_unlock(&entries_mutex);
+	return;
 
 	hashmap_insert(entries_original_key, new_entry->filename_original,
 		       new_entry);
@@ -832,6 +847,21 @@ update_fuse_entry_from_original(const char *filename)
 	pthread_mutex_unlock(&entries_mutex);
 }
 
+int
+update_fuse_entry_delete(const char *file)
+{
+	pthread_mutex_lock(&entries_mutex);
+	// Strip path, validate just the new basename
+	const char *keyname = strrchr(file, '/');
+	keyname++;
+	LOG("Found entry to delete");
+	hashmap_remove(entries_original_key, keyname);
+	// Frees
+	hashmap_remove(entries_fuse_key, keyname);
+	pthread_mutex_unlock(&entries_mutex);
+	return 0;
+}
+
 void *
 watch_ics_dir(void *arg)
 {
@@ -842,7 +872,8 @@ watch_ics_dir(void *arg)
 	}
 
 	// TODO: Fix IN_DELETE
-	int wd = inotify_add_watch(fd, ICS_DIR, IN_CREATE | IN_MODIFY);
+	int wd =
+	    inotify_add_watch(fd, ICS_DIR, IN_CREATE | IN_MODIFY | IN_DELETE);
 
 	if (wd < 0) {
 		perror("inotify_add_watch");
@@ -875,8 +906,17 @@ watch_ics_dir(void *arg)
 				LOG("Detected change in ICS file: %s\n",
 				    event->name);
 
-				// Reload entries on any change
-				update_fuse_entry_from_original(full_path);
+				if (event->mask & IN_DELETE) {
+					update_fuse_entry_delete(full_path);
+				}
+				else if (event->mask & IN_MODIFY) {
+
+					// Reload entries on any change
+					update_or_create_fuse_entry_from_original(
+					    full_path);
+				}
+				else if (event->mask & IN_CREATE) {
+				}
 			}
 			free(full_path);
 
@@ -889,10 +929,19 @@ watch_ics_dir(void *arg)
 	return NULL;
 }
 
+// Ignore for now
+static int
+journal_utimens(const char *path, const struct timespec tv[2],
+		struct fuse_file_info *info)
+{
+	return 0;
+}
+
 static const struct fuse_operations journal_oper = {.getattr = journal_getattr,
 						    .readdir = journal_readdir,
 						    .open = journal_open,
 						    .read = journal_read,
+						    .utimens = journal_utimens,
 						    .write = journal_write,
 						    .create = journal_create,
 						    .unlink = journal_unlink,