agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit bdd8da33645557dbd7b88d1ba6cecf6e93bba807
parent 56916c4a5f16c74268f6c084c05eb940dec61f61
Author: Marc Coquand <marc@coquand.email>
Date: Fri, 30 May 2025 22:10:17 +0100
Clean up parsing logic
Diffstat:
| M | main.c | | | 605 | ++++++++++++++++++++++++++++++++++++++++--------------------------------------- |
1 file changed, 304 insertions(+), 301 deletions(-)
diff --git a/main.c b/main.c
@@ -12,347 +12,350 @@
#define ICS_DIR "./sample_ics"
+const char *display_date_fmt = "%b %d, %Y at %H:%M";
+
typedef struct {
- char *filename;
- char *summary;
- char *description;
- time_t timestamp;
- time_t created_at;
+ char *filename;
+ char *summary;
+ char *description;
+ time_t timestamp;
+ time_t created_at;
} journal_entry;
static journal_entry *entries = NULL;
static size_t entry_count = 0;
static void free_entry(journal_entry *entry) {
- if (entry) {
- free(entry->filename);
- free(entry->summary);
- free(entry->description);
- }
+ if (entry) {
+ free(entry->filename);
+ free(entry->summary);
+ free(entry->description);
+ }
}
static void free_all_entries() {
- for (size_t i = 0; i < entry_count; i++) {
- free_entry(&entries[i]);
- }
- free(entries);
+ for (size_t i = 0; i < entry_count; i++) {
+ free_entry(&entries[i]);
+ }
+ 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;
+ 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) {
- perror("Failed to open .ics file");
- return -1;
- }
-
- // Read the whole file into a string
- fseek(file, 0, SEEK_END);
- long file_size = ftell(file);
- fseek(file, 0, SEEK_SET);
-
- char *file_content = malloc(file_size + 1);
- if (!file_content) {
- perror("Memory allocation failed");
- fclose(file);
- return -1;
- }
- fread(file_content, 1, file_size, file);
- file_content[file_size] = '\0'; // Null-terminate the string
- fclose(file);
-
- // Regular expressions for SUMMARY and DESCRIPTION fields
- 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:([^\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;
- }
-
- // Extract the SUMMARY
- if (regexec(®ex_summary, file_content, 2, matches_summary, 0) == 0) {
- size_t summary_len = matches_summary[1].rm_eo - matches_summary[1].rm_so;
- entry->summary = malloc(summary_len + 1);
- if (!entry->summary) {
- perror("Memory allocation failed for SUMMARY");
- free(file_content);
- regfree(®ex_summary);
- regfree(®ex_description);
- return -1;
- }
- strncpy(entry->summary, file_content + matches_summary[1].rm_so, summary_len);
- entry->summary[summary_len] = '\0';
- } else {
- fprintf(stderr, "SUMMARY field not found in: %s\n", file_path);
- free(file_content);
- regfree(®ex_summary);
- regfree(®ex_description);
- return -1;
- }
+static int compile_regexes(regex_t *regex_summary, regex_t *regex_description,
+ regex_t *regex_dtstamp, regex_t *regex_lastmod) {
+ if (regcomp(regex_summary, "SUMMARY:([^\r\n]+)", REG_EXTENDED) != 0) return -1;
+ if (regcomp(regex_description, "DESCRIPTION:([^\r\n]*)", REG_EXTENDED) != 0) return -1;
+ 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;
+ return 0;
+}
- // 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); // +1 for null-termination
- if (!entry->description) {
- perror("Memory allocation failed for DESCRIPTION");
- free(file_content);
- regfree(®ex_summary);
- regfree(®ex_description);
- regfree(®ex_dtstamp);
- regfree(®ex_lastmod);
- return -1;
- }
-
- // 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);
- regfree(®ex_summary);
- regfree(®ex_description);
- regfree(®ex_dtstamp);
- regfree(®ex_lastmod);
- 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
- }
+static char *extract_match(const char *content, regmatch_t match) {
+ size_t len = match.rm_eo - match.rm_so;
+ char *str = malloc(len + 1);
+ if (!str) return NULL;
+ memcpy(str, content + match.rm_so, len);
+ str[len] = '\0';
+ return str;
+}
- // Set timestamp to the current time
-
- // 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;
- }
+static void unescape_description(char *desc) {
+ char *pos;
+ while ((pos = strstr(desc, "\\n")) != NULL) {
+ *pos = '\n';
+ memmove(pos + 1, pos + 2, strlen(pos + 2) + 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;
- }
+ // Commas are saved as \,
+ while ((pos = strstr(desc, "\\,")) != NULL) {
+ *pos = ',';
+ memmove(pos + 1, pos + 2, strlen(pos + 2) + 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);
- regfree(®ex_description);
- return 0;
+static int parse_ics_file(const char *file_path, journal_entry *entry) {
+ FILE *file = fopen(file_path, "r");
+ if (!file) {
+ perror("Failed to open .ics file");
+ return -1;
+ }
+
+ // Read the whole file into a string
+ fseek(file, 0, SEEK_END);
+ long file_size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+
+ char *file_content = malloc(file_size + 1);
+ if (!file_content) {
+ perror("Memory allocation failed");
+ fclose(file);
+ return -1;
+ }
+ fread(file_content, 1, file_size, file);
+ file_content[file_size] = '\0'; // Null-terminate the string
+ fclose(file);
+
+ regex_t regex_summary, regex_description, regex_dtstamp, regex_lastmod;
+ regmatch_t matches_summary[2], matches_description[2], matches_dtstamp[2], matches_lastmod[2];
+
+ size_t return_code = -1;
+
+ if (compile_regexes(®ex_summary, ®ex_description, ®ex_dtstamp, ®ex_lastmod) != 0) {
+ fprintf(stderr, "Failed to compile regular expressions\n");
+ goto cleanup;
+ }
+
+ // Extract SUMMARY
+ if (regexec(®ex_summary, file_content, 2, matches_summary, 0) != 0) {
+ fprintf(stderr, "SUMMARY field not found in: %s\n", file_path);
+ goto cleanup;
+ }
+
+ entry->summary = extract_match(file_content, matches_summary[1]);
+ if (!entry->summary) {
+ perror("Memory allocation failed for SUMMARY");
+ goto cleanup;
+ }
+
+
+ // Extract DESCRIPTION
+ if (regexec(®ex_description, file_content, 2, matches_description, 0) != 0) {
+ fprintf(stderr, "DESCRIPTION field not found in: %s\n", file_path);
+ goto cleanup;
+ }
+ entry->description = extract_match(file_content, matches_description[1]);
+ if (!entry->description) {
+ perror("Memory allocation failed for DESCRIPTION");
+ goto cleanup;
+ }
+ unescape_description(entry->description);
+
+
+ // Extract the DTSTAMP, our created_at
+ if (regexec(®ex_dtstamp, file_content, 2, matches_dtstamp, 0) == 0) {
+ char *dtstamp_str = extract_match(file_content, matches_dtstamp[1]);
+ if (!dtstamp_str) {
+ perror("Memory allocation failed for DTSTAMP");
+ goto cleanup;
+ }
+ entry->created_at = parse_datetime(dtstamp_str);
+ free(dtstamp_str);
+ if (entry->created_at == -1) {
+ fprintf(stderr, "Failed to parse DTSTAMP in: %s\n", file_path);
+ goto cleanup;
+ }
+ } else {
+ entry->created_at = time(NULL);
+ }
+
+ if (regexec(®ex_dtstamp, file_content, 2, matches_dtstamp, 0) == 0) {
+ char *dtstamp_str = extract_match(file_content, matches_dtstamp[1]);
+ if (!dtstamp_str) {
+ perror("Memory allocation failed for DTSTAMP");
+ goto cleanup;
+ }
+ entry->created_at = parse_datetime(dtstamp_str);
+ free(dtstamp_str);
+ if (entry->created_at == -1) {
+ fprintf(stderr, "Failed to parse DTSTAMP in: %s\n", file_path);
+ goto cleanup;
+ }
+ } else {
+ entry->created_at = time(NULL);
+ }
+
+ // LAST-MODIFIED (timestamp)
+ if (regexec(®ex_lastmod, file_content, 2, matches_lastmod, 0) == 0) {
+ char *lastmod_str = extract_match(file_content, matches_lastmod[1]);
+ if (!lastmod_str) {
+ perror("Memory allocation failed for LAST-MODIFIED");
+ goto cleanup;
+ }
+ entry->timestamp = parse_datetime(lastmod_str);
+ free(lastmod_str);
+ if (entry->timestamp == -1) {
+ fprintf(stderr, "Failed to parse LAST-MODIFIED in: %s\n", file_path);
+ goto cleanup;
+ }
+ } else {
+ entry->timestamp = entry->created_at; // fallback
+ }
+
+ // Success and goto cleanup
+ return_code = 0;
+
+cleanup:
+ free(file_content);
+ regfree(®ex_summary);
+ regfree(®ex_description);
+ regfree(®ex_dtstamp);
+ regfree(®ex_lastmod);
+ return return_code;
}
static void load_journal_entries() {
- printf("Loading journal entries from: %s\n", ICS_DIR);
- DIR *dir = opendir(ICS_DIR);
- if (!dir) {
- perror("opendir");
- return;
- }
-
- struct dirent *entry;
- char filepath[512];
- while ((entry = readdir(dir))) {
- if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
- snprintf(filepath, sizeof(filepath), "%s/%s", ICS_DIR, entry->d_name);
- journal_entry new_entry;
- new_entry.filename = strdup(entry->d_name);
- if (parse_ics_file(filepath, &new_entry) == 0) {
- entries = realloc(entries, sizeof(journal_entry) * (entry_count + 1));
- entries[entry_count] = new_entry;
- entry_count++;
- } else {
- free(new_entry.filename);
- }
- }
- }
- printf("Loaded %zu journal entries.\n", entry_count);
- closedir(dir);
+ printf("Loading journal entries from: %s\n", ICS_DIR);
+ DIR *dir = opendir(ICS_DIR);
+ if (!dir) {
+ perror("opendir");
+ return;
+ }
+
+ struct dirent *entry;
+ char filepath[512];
+ while ((entry = readdir(dir))) {
+ if (entry->d_type == DT_REG && strstr(entry->d_name, ".ics")) {
+ snprintf(filepath, sizeof(filepath), "%s/%s", ICS_DIR, entry->d_name);
+ journal_entry new_entry;
+ new_entry.filename = strdup(entry->d_name);
+ if (parse_ics_file(filepath, &new_entry) == 0) {
+ entries = realloc(entries, sizeof(journal_entry) * (entry_count + 1));
+ entries[entry_count] = new_entry;
+ entry_count++;
+ } else {
+ free(new_entry.filename);
+ }
+ }
+ }
+ printf("Loaded %zu journal entries.\n", entry_count);
+ closedir(dir);
}
static int journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi) {
- memset(stbuf, 0, sizeof(struct stat));
-
- if (strcmp(path, "/") == 0) {
- stbuf->st_mode = S_IFDIR | 0755;
- stbuf->st_nlink = 2;
- return 0;
- }
-
- for (size_t i = 0; i < entry_count; i++) {
- char entry_path[256];
- snprintf(entry_path, sizeof(entry_path), "/%s.txt", entries[i].filename);
- if (strcmp(path, entry_path) == 0) {
- stbuf->st_mode = S_IFREG | 0444;
- stbuf->st_nlink = 1;
- struct tm *tm_info = localtime(&entries[i].timestamp);
- char timestamp_str[64];
- strftime(timestamp_str, sizeof(timestamp_str), "%b %d, %Y at %H:%M", tm_info);
-
- // Calculate the file size as in journal_read
- int size = snprintf(NULL, 0, "%s\n%s\n---\n%s\n",
- entries[i].summary, timestamp_str, entries[i].description);
-
- stbuf->st_size = (off_t)size;
-
- // 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;
+ memset(stbuf, 0, sizeof(struct stat));
+
+ if (strcmp(path, "/") == 0) {
+ stbuf->st_mode = S_IFDIR | 0755;
+ stbuf->st_nlink = 2;
+ return 0;
+ }
+
+ for (size_t i = 0; i < entry_count; i++) {
+ char entry_path[256];
+ snprintf(entry_path, sizeof(entry_path), "/%s.txt", entries[i].filename);
+ if (strcmp(path, entry_path) == 0) {
+ stbuf->st_mode = S_IFREG | 0444;
+ stbuf->st_nlink = 1;
+ struct tm *tm_info = localtime(&entries[i].timestamp);
+ char timestamp_str[64];
+ strftime(timestamp_str, sizeof(timestamp_str), display_date_fmt, tm_info);
+
+ // Calculate the file size as in journal_read
+ int size = snprintf(NULL, 0, "%s\n%s\n---\n%s\n",
+ entries[i].summary, timestamp_str, entries[i].description);
+
+ stbuf->st_size = (off_t)size;
+
+ // 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) {
- if (strcmp(path, "/") != 0)
- return -ENOENT;
-
- filler(buf, ".", NULL, 0, 0);
- filler(buf, "..", NULL, 0, 0);
-
- for (size_t i = 0; i < entry_count; i++) {
- char entry_name[256];
- snprintf(entry_name, sizeof(entry_name), "%s.txt", entries[i].filename);
- filler(buf, entry_name, NULL, 0, 0);
- }
+ off_t offset, struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags) {
+ if (strcmp(path, "/") != 0)
+ return -ENOENT;
- return 0;
+ filler(buf, ".", NULL, 0, 0);
+ filler(buf, "..", NULL, 0, 0);
+
+ for (size_t i = 0; i < entry_count; i++) {
+ char entry_name[256];
+ snprintf(entry_name, sizeof(entry_name), "%s.txt", entries[i].filename);
+ filler(buf, entry_name, NULL, 0, 0);
+ }
+
+ return 0;
}
static int journal_open(const char *path, struct fuse_file_info *fi) {
- for (size_t i = 0; i < entry_count; i++) {
- char entry_path[256];
- snprintf(entry_path, sizeof(entry_path), "/%s.txt", entries[i].filename);
- if (strcmp(path, entry_path) == 0) {
- return 0;
- }
- }
- return -ENOENT;
+ for (size_t i = 0; i < entry_count; i++) {
+ char entry_path[256];
+ snprintf(entry_path, sizeof(entry_path), "/%s.txt", entries[i].filename);
+ if (strcmp(path, entry_path) == 0) {
+ return 0;
+ }
+ }
+ return -ENOENT;
}
static int journal_read(const char *path, char *buf, size_t size, off_t offset,
- struct fuse_file_info *fi) {
- for (size_t i = 0; i < entry_count; i++) {
- char entry_path[256];
- snprintf(entry_path, sizeof(entry_path), "/%s.txt", entries[i].filename);
-
- if (strcmp(path, entry_path) == 0) {
- // Format timestamp
- struct tm *tm_info = localtime(&entries[i].timestamp);
- char timestamp_str[64];
- strftime(timestamp_str, sizeof(timestamp_str), "%b %d, %Y at %H:%M", tm_info);
-
- // Build full content string using snprintf(NULL, 0) to get length
- int needed_len = snprintf(NULL, 0, "%s\n%s\n---\n%s\n",
- entries[i].summary, timestamp_str, entries[i].description);
- if (needed_len < 0)
- return -EIO; // snprintf error
-
- char *full_content = malloc(needed_len + 1);
- if (!full_content) {
- perror("Memory allocation failed");
- return -ENOMEM;
- }
-
- snprintf(full_content, needed_len + 1, "%s\n%s\n---\n%s\n",
- entries[i].summary, timestamp_str, entries[i].description);
-
- size_t len = (size_t)needed_len;
-
- if (offset >= len) {
- free(full_content);
- return 0; // EOF
- }
-
- if (offset + size > len)
- size = len - offset;
-
- memcpy(buf, full_content + offset, size);
-
- free(full_content);
- return size;
- }
- }
- return -ENOENT;
+ struct fuse_file_info *fi) {
+ for (size_t i = 0; i < entry_count; i++) {
+ char entry_path[256];
+ snprintf(entry_path, sizeof(entry_path), "/%s.txt", entries[i].filename);
+
+ if (strcmp(path, entry_path) == 0) {
+ // Format timestamp
+ struct tm *tm_info = localtime(&entries[i].created_at);
+ char timestamp_str[64];
+ strftime(timestamp_str, sizeof(timestamp_str), display_date_fmt, tm_info);
+
+ // Build full content string using snprintf(NULL, 0) to get length
+ int needed_len = (size_t)snprintf(NULL, 0, "%s\n%s\n---\n%s\n",
+ entries[i].summary, timestamp_str, entries[i].description);
+ if (needed_len < 0)
+ return -EIO; // snprintf error
+
+ char *full_content = malloc(needed_len + 1);
+ if (!full_content) {
+ perror("Memory allocation failed");
+ return -ENOMEM;
+ }
+
+ snprintf(full_content, needed_len + 1, "%s\n%s\n---\n%s\n",
+ entries[i].summary, timestamp_str, entries[i].description);
+
+
+ if (offset >= needed_len) {
+ free(full_content);
+ return 0; // EOF
+ }
+
+ if (offset + size > needed_len)
+ size = needed_len - offset;
+
+ memcpy(buf, full_content + offset, size);
+
+ free(full_content);
+ return size;
+ }
+ }
+ return -ENOENT;
}
static const struct fuse_operations journal_oper = {
- .getattr = journal_getattr,
- .readdir = journal_readdir,
- .open = journal_open,
- .read = journal_read,
+ .getattr = journal_getattr,
+ .readdir = journal_readdir,
+ .open = journal_open,
+ .read = journal_read,
};
int main(int argc, char *argv[]) {
- load_journal_entries();
- int ret = fuse_main(argc, argv, &journal_oper, NULL);
+ load_journal_entries();
+ int ret = fuse_main(argc, argv, &journal_oper, NULL);
- // Cleanup before exit
- free_all_entries();
+ // Cleanup before exit
+ free_all_entries();
- return ret;
+ return ret;
}