agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit c39eefc45b224482cb68eb52f83fe397d5cf247d
parent cca40f2c0bba3c84fb88b5c5dfe8562321e0ebc5
Author: Marc Coquand <marc@coquand.email>
Date: Tue, 3 Jun 2025 11:54:33 +0100
cleanup
Diffstat:
| M | main.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(®ex_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(®ex_dtstamp, ®ex_lastmod, ®ex_fuse_file) !=
- 0) {
+ if (compile_regexes(®ex_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(®ex_dtstamp);
- regfree(®ex_lastmod);
regfree(®ex_fuse_file);
LOG("Regexp freed");
exit(ret);