agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 06037496d64e767850cdea46d260e587af288b45
parent 9666d06c8660caedcd5f0bffa70bd951a5f6abcb
Author: Marc Coquand <marc@coquand.email>
Date: Fri, 30 May 2025 20:01:27 +0100
Support multiline, createdat
Diffstat:
2 files changed, 99 insertions(+), 11 deletions(-)
diff --git a/main.c b/main.c
@@ -17,8 +17,8 @@ typedef struct {
char *summary;
char *description;
time_t timestamp;
+ time_t created_at;
} journal_entry;
-
static journal_entry *entries = NULL;
static size_t entry_count = 0;
@@ -37,6 +37,20 @@ static void free_all_entries() {
free(entries);
}
+static 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 -1;
+}
+
static int parse_ics_file(const char *file_path, journal_entry *entry) {
FILE *file = fopen(file_path, "r");
if (!file) {
@@ -60,12 +74,16 @@ static int parse_ics_file(const char *file_path, journal_entry *entry) {
fclose(file);
// Regular expressions for SUMMARY and DESCRIPTION fields
- regex_t regex_summary, regex_description;
- regmatch_t matches_summary[2], matches_description[2];
+ regex_t regex_summary, regex_description, regex_dtstamp, regex_lastmod;
+ regmatch_t matches_summary[2], matches_description[2], matches_dtstamp[2], matches_lastmod[2];
// Compile regular expressions
if (regcomp(®ex_summary, "SUMMARY:([^\r\n]+)", REG_EXTENDED) != 0 ||
- regcomp(®ex_description, "DESCRIPTION:([^\n\r]*)", REG_EXTENDED) != 0) {
+ regcomp(®ex_description, "DESCRIPTION:([^\r\n]*)", REG_EXTENDED) != 0 ||
+ regcomp(®ex_dtstamp, "DTSTAMP:([0-9T]+Z)", REG_EXTENDED) != 0 ||
+ regcomp(®ex_lastmod, "LAST-MODIFIED:([0-9T]+Z)", REG_EXTENDED) != 0) {
+
+
fprintf(stderr, "Failed to compile regular expressions\n");
free(file_content);
return -1;
@@ -92,10 +110,10 @@ static int parse_ics_file(const char *file_path, journal_entry *entry) {
return -1;
}
- // Extract the DESCRIPTION
+ // Extract the DESCRIPTION (including multiline handling)
if (regexec(®ex_description, file_content, 2, matches_description, 0) == 0) {
size_t description_len = matches_description[1].rm_eo - matches_description[1].rm_so;
- entry->description = malloc(description_len + 1);
+ entry->description = malloc(description_len + 1); // +1 for null-termination
if (!entry->description) {
perror("Memory allocation failed for DESCRIPTION");
free(file_content);
@@ -103,9 +121,17 @@ static int parse_ics_file(const char *file_path, journal_entry *entry) {
regfree(®ex_description);
return -1;
}
- strncpy(entry->description, file_content + matches_description[1].rm_so, description_len);
- entry->description[description_len] = '\0';
- printf("Matched DESCRIPTION: %s\n", entry->description); // Debugging line
+
+ // Copy the string using memcpy and ensure null termination
+ memcpy(entry->description, file_content + matches_description[1].rm_so, description_len);
+ entry->description[description_len] = '\0'; // Ensure null-termination
+
+ // Replace literal "\n" with actual newlines
+ char *newline_pos = NULL;
+ while ((newline_pos = strstr(entry->description, "\\n")) != NULL) {
+ *newline_pos = '\n'; // Replace literal '\n' with actual newline
+ memmove(newline_pos + 1, newline_pos + 2, strlen(newline_pos + 2) + 1); // Shift the rest
+ }
} else {
fprintf(stderr, "DESCRIPTION field not found in: %s\n", file_path);
free(file_content);
@@ -113,10 +139,56 @@ static int parse_ics_file(const char *file_path, journal_entry *entry) {
regfree(®ex_description);
return -1;
}
+ char *escaped_comma_pos = NULL;
+ while ((escaped_comma_pos = strstr(entry->description, "\\,")) != NULL) {
+ *escaped_comma_pos = ','; // Replace escaped comma with actual comma
+ memmove(escaped_comma_pos + 1, escaped_comma_pos + 2, strlen(escaped_comma_pos + 2) + 1); // Shift the rest of the string
+ }
// Set timestamp to the current time
- entry->timestamp = time(NULL);
+ // Extract the DTSTAMP
+ if (regexec(®ex_dtstamp, file_content, 2, matches_dtstamp, 0) == 0) {
+ size_t dtstamp_len = matches_dtstamp[1].rm_eo - matches_dtstamp[1].rm_so;
+ char dtstamp_str[dtstamp_len + 1];
+ strncpy(dtstamp_str, file_content + matches_dtstamp[1].rm_so, dtstamp_len);
+ dtstamp_str[dtstamp_len] = '\0';
+
+ entry->created_at = parse_datetime(dtstamp_str);
+ if (entry->created_at == -1) {
+ fprintf(stderr, "Failed to parse DTSTAMP in: %s\n", file_path);
+ free(file_content);
+ regfree(®ex_summary);
+ regfree(®ex_description);
+ regfree(®ex_dtstamp);
+ regfree(®ex_lastmod);
+ return -1;
+ }
+ }
+
+ // Extract the LAST-MODIFIED (if present)
+ else if (regexec(®ex_lastmod, file_content, 2, matches_lastmod, 0) == 0) {
+ size_t lastmod_len = matches_lastmod[1].rm_eo - matches_lastmod[1].rm_so;
+ char lastmod_str[lastmod_len + 1];
+ strncpy(lastmod_str, file_content + matches_lastmod[1].rm_so, lastmod_len);
+ lastmod_str[lastmod_len] = '\0';
+
+ entry->timestamp = parse_datetime(lastmod_str);
+ if (entry->timestamp == -1) {
+ fprintf(stderr, "Failed to parse LAST-MODIFIED in: %s\n", file_path);
+ free(file_content);
+ regfree(®ex_summary);
+ regfree(®ex_description);
+ regfree(®ex_dtstamp);
+ regfree(®ex_lastmod);
+ return -1;
+ }
+ }
+
+ // If neither DTSTAMP nor LAST-MODIFIED found, set timestamp to current time
+ if (entry->timestamp == 0) {
+ entry->timestamp = time(NULL);
+ }
// Clean up
free(file_content);
regfree(®ex_summary);
@@ -168,13 +240,19 @@ static int journal_getattr(const char *path, struct stat *stbuf, struct fuse_fil
stbuf->st_mode = S_IFREG | 0444;
stbuf->st_nlink = 1;
stbuf->st_size = strlen(entries[i].summary) + strlen(entries[i].description) + 2;
+
+ // Set the times
+ stbuf->st_mtime = entries[i].timestamp; // Use the timestamp as the modification time
+ stbuf->st_ctime = entries[i].timestamp; // Set the change time to the same as the modification time
+ // Access time is current time
+ stbuf->st_atime = time(NULL);
+
return 0;
}
}
return -ENOENT;
}
-
static int journal_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags) {
diff --git a/sample_ics/2025-01-04.ics b/sample_ics/2025-01-04.ics
@@ -0,0 +1,10 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:20251110T120000Z-002@host.com
+DTSTAMP:20251110T120000Z
+SUMMARY:Team Lunch
+DESCRIPTION:Lunch at The Bistro to celebrate recent achievements.
+LAST-MODIFIED:20251110T130000Z
+END:VEVENT
+END:VCALENDAR