agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit c39eefc45b224482cb68eb52f83fe397d5cf247d
parent cca40f2c0bba3c84fb88b5c5dfe8562321e0ebc5
Author: Marc Coquand <marc@coquand.email>
Date:   Tue,  3 Jun 2025 11:54:33 +0100

cleanup

Diffstat:
Mmain.c | 266++++++++++++++++---------------------------------------------------------------
1 file changed, 54 insertions(+), 212 deletions(-)
diff --git a/main.c b/main.c
@@ -27,7 +27,7 @@
 char ICS_DIR[256];
 
 const char *date_title_fmt = "%Y%m%d-%H:%M";
-regex_t regex_dtstamp, regex_lastmod, regex_fuse_file;
+regex_t regex_fuse_file;
 
 time_t INVALID_TIME = (time_t)-1;
 
@@ -46,6 +46,12 @@ struct hashmap *entries_fuse_key = NULL;
 struct hashmap *entries_original_key = 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());
+}
 
 void
 free_journal_entry(void *val)
@@ -62,85 +68,9 @@ free_journal_entry(void *val)
 	free(entry);
 }
 
-time_t
-parse_datetime(const char *datetime)
-{
-	struct tm tm = {0};
-	int ret =
-	    sscanf(datetime, "%4d%2d%2dT%2d%2d%2dZ", &tm.tm_year, &tm.tm_mon,
-		   &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
-	if (ret == 6) {
-		tm.tm_year -= 1900; // tm_year is years since 1900
-		tm.tm_mon -= 1;	    // tm_mon is months since January (0-11)
-		tm.tm_isdst =
-		    -1; // Let mktime determine if daylight savings is needed
-		return mktime(&tm);
-	}
-	return INVALID_TIME;
-}
-
-// Folds are [\r]\n followed by ' ' or '\t'
 int
-skip_fold(const char *p)
-{
-	// '\r\n' + ' ' OR '\t'
-	if (*p == '\r' && p[1] == '\n' && (p[2] == ' ' || p[2] == '\t'))
-		return 3;
-
-	// '\n' + ' ' OR '\t'
-	if (*p == '\n' && (p[1] == ' ' || p[1] == '\t'))
-		return 2;
-
-	return 0;
-}
-
-// Description is multiline and cuts of at 75 characters.
-// Once extracted it will also need to be escaped.
-// Field name should be without colon, I.E.
-// "DESCRIPTION"
-char *
-extract_ical_field(const char *ical, const char *field_name)
+compile_regexes(regex_t *regex_fuse_file)
 {
-	size_t field_len = strlen(field_name);
-	const char *start = strstr(ical, field_name);
-
-	start += field_len;
-
-	// skip the colon
-	start++;
-
-	const char *p = start;
-	char *result = xcalloc(strlen(p) + 1, sizeof(char));
-
-	size_t len = 0;
-
-	while (*p) {
-		p += skip_fold(p);
-
-		// end of field
-		if ((*p == '\r' && p[1] == '\n') || *p == '\n') {
-			break;
-		}
-
-		result[len++] = *p++;
-	}
-
-	result[len] = '\0';
-
-	// Shrink the buffer to the actual size
-	char *final = realloc(result, len + 1);
-	return final;
-}
-
-int
-compile_regexes(regex_t *regex_dtstamp, regex_t *regex_lastmod,
-		regex_t *regex_fuse_file)
-{
-	if (regcomp(regex_dtstamp, "DTSTAMP:([0-9T]+Z)", REG_EXTENDED) != 0)
-		return -1;
-	if (regcomp(regex_lastmod, "LAST-MODIFIED:([0-9T]+Z)", REG_EXTENDED) !=
-	    0)
-		return -1;
 	if (regcomp(regex_fuse_file,
 		    "^([0-9]{8})-([0-9]{2}:[0-9]{2})(_[^/\\:*?\"<>|]+)?\\.txt$",
 		    REG_EXTENDED) != 0)
@@ -148,71 +78,6 @@ compile_regexes(regex_t *regex_dtstamp, regex_t *regex_lastmod,
 	return 0;
 }
 
-// Extracts the matched text from regexp
-char *
-extract_match(const char *content, regmatch_t match)
-{
-	size_t len = match.rm_eo - match.rm_so;
-	char *m_str = xcalloc(len + 1, sizeof(char));
-	memcpy(m_str, content + match.rm_so, len);
-	m_str[len] = '\0';
-	return m_str;
-}
-
-// RFC 5545 specifies how human-readable text is escaped
-// https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.11
-char *
-escape_text(const char *src)
-{
-	size_t len = strlen(src);
-	// Safe amount
-	size_t max_len = len * 2;
-	char *escaped = calloc(max_len, 1);
-	char *dst = escaped;
-	int line_len = 0;
-
-	for (const char *p = src; *p; ++p) {
-		int esc_len =
-		    (*p == '\n' || *p == '\\' || *p == ',' || *p == ';') ? 2
-									 : 1;
-
-		if (line_len + esc_len > 75) {
-			*dst++ = '\r';
-			*dst++ = '\n';
-			*dst++ = ' ';
-			line_len = 1;
-		}
-
-		switch (*p) {
-		case '\n':
-			*dst++ = '\\';
-			*dst++ = 'n';
-			break;
-		case '\\':
-			*dst++ = '\\';
-			*dst++ = '\\';
-			break;
-		case ',':
-			*dst++ = '\\';
-			*dst++ = ',';
-			break;
-		case ';':
-			*dst++ = '\\';
-			*dst++ = ';';
-			break;
-		default:
-			*dst++ = *p;
-			break;
-		}
-
-		line_len += esc_len;
-	}
-
-	*dst = '\0';
-	size_t actual_len = strlen(escaped) + 1;
-	return realloc(escaped, actual_len);
-}
-
 journal_entry *
 get_entry_from_fuse_path(const char *path)
 {
@@ -264,6 +129,7 @@ parse_ics_file(const char *filename)
 	return component;
 }
 
+// We assume that there is only one entry in the file
 int
 parse_ics_to_journal_entry(const char *filename, journal_entry *entry)
 {
@@ -272,6 +138,7 @@ parse_ics_to_journal_entry(const char *filename, journal_entry *entry)
 		LOG("No component");
 		return -1;
 	}
+	// Verify if necessary?
 	icalcomponent *journal =
 	    icalcomponent_get_first_real_component(component);
 	if (journal == NULL) {
@@ -360,7 +227,7 @@ load_journal_entries()
 		if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
 			if (asprintf(&filepath, "%s/%s", ICS_DIR,
 				     entry->d_name) == -1) {
-				perror("aspintf fialed");
+				perror("aspintf failed");
 				continue;
 			}
 
@@ -372,7 +239,7 @@ load_journal_entries()
 				perror("strdup failed");
 
 				free(filepath);
-				free(new_entry);
+				free_journal_entry(new_entry);
 				continue;
 			}
 
@@ -380,9 +247,7 @@ load_journal_entries()
 			    0) {
 				LOG("Failed to parse entry: %s", entry->d_name);
 
-				free(filepath);
-				free(new_entry->filename_original);
-				free(new_entry);
+				free_journal_entry(new_entry);
 				continue;
 			}
 
@@ -396,9 +261,7 @@ load_journal_entries()
 				LOG("Failed to generate filename for %s",
 				    entry->d_name);
 
-				free(filepath);
-				free(new_entry->filename_original);
-				free(new_entry);
+				free_journal_entry(new_entry);
 				continue;
 			}
 
@@ -412,9 +275,7 @@ load_journal_entries()
 				LOG("Failed to insert entry");
 
 				free(filepath);
-				free(new_entry->filename);
-				free(new_entry->filename_original);
-				free(new_entry);
+				free_journal_entry(new_entry);
 				continue;
 			}
 
@@ -452,6 +313,23 @@ get_entry_content_size(journal_entry *entry)
 	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);
+	}
+}
+
 int
 journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 {
@@ -481,21 +359,12 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
 	stbuf->st_size = (off_t)size;
 
 	LOG("Entry found: %s", entry->filename);
-	icalproperty *last_modified_prop = icalcomponent_get_next_property(
-	    entry->component, ICAL_LASTMODIFIED_PROPERTY);
+	icaltimetype ical_last_modified = get_last_modified(entry);
 
-	time_t lastmodified;
-	if (last_modified_prop) {
-		icaltimetype ical_lastmodified =
-		    icalproperty_get_lastmodified(last_modified_prop);
-		lastmodified = icaltime_as_timet(ical_lastmodified);
-	}
-	else {
-		lastmodified = time(NULL);
-	}
+	time_t last_modified = icaltime_as_timet(ical_last_modified);
 
-	stbuf->st_mtime = lastmodified;
-	stbuf->st_ctime = lastmodified;
+	stbuf->st_mtime = last_modified;
+	stbuf->st_ctime = last_modified;
 	stbuf->st_atime = time(NULL);
 
 	ret_code = 0;
@@ -594,14 +463,6 @@ unlock_and_return:
 	return ret_code;
 }
 
-icaltimetype
-get_now()
-{
-	struct icaltimetype now_tt = icaltime_from_timet_with_zone(
-	    time(NULL), 0, icaltimezone_get_utc_timezone());
-	return now_tt;
-}
-
 size_t
 parse_entry_content(journal_entry *entry, const char *buf, size_t size)
 {
@@ -638,11 +499,11 @@ write_entry_to_ical_file(const journal_entry *entry, const char *filepath)
 	icalproperty *prop = icalcomponent_get_first_property(
 	    entry->component, ICAL_LASTMODIFIED_PROPERTY);
 	if (!prop) {
-		prop = icalproperty_new_lastmodified(get_now());
+		prop = icalproperty_new_lastmodified(get_ical_now());
 		icalcomponent_add_property(entry->component, prop);
 	}
 	else {
-		icalproperty_set_lastmodified(prop, get_now());
+		icalproperty_set_lastmodified(prop, get_ical_now());
 	}
 
 	char *ical_str = icalcomponent_as_ical_string_r(entry->component);
@@ -719,35 +580,24 @@ timestamp_from_filename(const char *filename)
 	if (regexec(&regex_fuse_file, filename, 3, matches, 0) != 0)
 		return INVALID_TIME;
 
-	// Extract YYYYMMDD and HH:MM
-	char date_str[9] = {0};
-	char time_str[6] = {0};
-
-	snprintf(date_str, sizeof(date_str), "%.*s",
-		 matches[1].rm_eo - matches[1].rm_so,
-		 filename + matches[1].rm_so);
-	snprintf(time_str, sizeof(time_str), "%.*s",
-		 matches[2].rm_eo - matches[2].rm_so,
-		 filename + matches[2].rm_so);
-
 	int year, month, day, hour, minute;
-	if (sscanf(date_str, "%4d%2d%2d", &year, &month, &day) != 3)
+
+	if (sscanf(filename + matches[1].rm_so, "%4d%2d%2d", &year, &month,
+		   &day) != 3)
 		return INVALID_TIME;
 
-	if (sscanf(time_str, "%2d:%2d", &hour, &minute) != 2)
+	if (sscanf(filename + matches[2].rm_so, "%2d:%2d", &hour, &minute) != 2)
 		return INVALID_TIME;
 
-	struct tm tm_time = {0};
-	tm_time.tm_year = year - 1900;
-	tm_time.tm_mon = month - 1;
-	tm_time.tm_mday = day;
-	tm_time.tm_hour = hour;
-	tm_time.tm_min = minute;
-	tm_time.tm_sec = 0;
-	tm_time.tm_isdst = -1;
-
-	time_t timestamp = mktime(&tm_time);
-	return timestamp;
+	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
@@ -773,8 +623,8 @@ do_journal_entry_rename(time_t new_created_at, const char *old, const char *new)
 
 	icalcomponent_set_dtstamp(new_entry->component, created_at_ical);
 
-	// hashmap frees old_entry
 	hashmap_remove(entries_original_key, old_entry->filename_original);
+	// remove handles freeing here
 	hashmap_remove(entries_fuse_key, old_entry->filename);
 
 	hashmap_insert(entries_original_key, new_entry->filename_original,
@@ -881,8 +731,7 @@ create_new_unique_ics_uid()
 icalcomponent *
 create_vjournal_entry(time_t created_at)
 {
-	struct icaltimetype created_at_ical =
-	    icaltime_from_timet_with_zone(created_at, 0, NULL);
+	struct icaltimetype created_at_ical = get_ical_now();
 
 	icalcomponent *calendar = icalcomponent_new_vcalendar();
 
@@ -900,10 +749,6 @@ create_vjournal_entry(time_t created_at)
 	icalcomponent_add_property(journal, icalproperty_new_summary(""));
 	icalcomponent_add_property(journal, icalproperty_new_description(""));
 
-	icalcomponent_add_property(journal,
-				   icalproperty_new_lastmodified(
-				       icaltime_current_time_with_zone(NULL)));
-
 	icalcomponent_add_component(calendar, journal);
 
 	return calendar;
@@ -1105,8 +950,7 @@ main(int argc, char *argv[])
 		return 1;
 	}
 
-	if (compile_regexes(&regex_dtstamp, &regex_lastmod, &regex_fuse_file) !=
-	    0) {
+	if (compile_regexes(&regex_fuse_file) != 0) {
 		fprintf(stderr, "Failed to compile regular expressions\n");
 		return -1;
 	}
@@ -1123,8 +967,6 @@ main(int argc, char *argv[])
 	hashmap_free(entries_original_key);
 	LOG("Hashmap freed");
 
-	regfree(&regex_dtstamp);
-	regfree(&regex_lastmod);
 	regfree(&regex_fuse_file);
 	LOG("Regexp freed");
 	exit(ret);