agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 40c4af71c6ca4926fc7da0b37030ea58f5f6f961
parent 58488c71d5cd19b609e87dd14915187a73b1d883
Author: Marc Coquand <marc@coquand.email>
Date: Mon, 2 Jun 2025 19:51:44 +0100
Convert to libical
Diffstat:
| M | main.c | | | 254 | ++++++++++++++++++++++++++++++------------------------------------------------- |
1 file changed, 95 insertions(+), 159 deletions(-)
diff --git a/main.c b/main.c
@@ -36,12 +36,7 @@ time_t INVALID_TIME = (time_t)-1;
typedef struct {
char *filename;
char *filename_original;
- char *summary;
- char *description;
- char *uid;
- char *categories;
- time_t modified_at;
- time_t created_at;
+ icalcomponent *component;
} journal_entry;
// We create two maps that link to the same entries
@@ -56,18 +51,12 @@ free_journal_entry(void *val)
journal_entry *entry = (journal_entry *)val;
if (!entry)
return;
- free(entry->uid);
- free(entry->filename);
- free(entry->filename_original);
- if (entry->summary != NULL && strcmp(entry->summary, "") != 0) {
- free(entry->summary);
- }
- if (entry->categories != NULL && strcmp(entry->summary, "") != 0) {
- free(entry->categories);
- }
- if (entry->description != NULL && strcmp(entry->description, "") != 0) {
- free(entry->description);
- }
+ if (entry->component)
+ icalcomponent_free(entry->component);
+ if (entry->filename)
+ free(entry->filename);
+ if (entry->filename_original)
+ free(entry->filename_original);
free(entry);
}
@@ -300,64 +289,15 @@ parse_ics_to_journal_entry(const char *filename, journal_entry *entry)
LOG("No journal component");
return -2;
}
-
- icalproperty *prop =
- icalcomponent_get_first_property(journal, ICAL_ANY_PROPERTY);
- while (prop != NULL) {
- icalproperty_kind kind = icalproperty_isa(prop);
- switch (kind) {
- case ICAL_SUMMARY_PROPERTY:
- free(entry->summary);
- entry->summary = strdup(icalproperty_get_summary(prop));
- break;
-
- case ICAL_DESCRIPTION_PROPERTY:
- free(entry->description);
- entry->description =
- strdup(icalproperty_get_description(prop));
- break;
-
- case ICAL_UID_PROPERTY:
- free(entry->uid);
- entry->uid = strdup(icalproperty_get_uid(prop));
- break;
-
- case ICAL_CATEGORIES_PROPERTY:
- free(entry->categories);
- entry->categories =
- strdup(icalproperty_get_categories(prop));
- break;
-
- case ICAL_LASTMODIFIED_PROPERTY: {
- struct icaltimetype tt = icalproperty_get_dtstamp(prop);
- entry->modified_at = icaltime_as_timet(tt);
- break;
- }
-
- case ICAL_DTSTAMP_PROPERTY: {
- struct icaltimetype tt = icalproperty_get_created(prop);
- entry->created_at = icaltime_as_timet(tt);
- break;
- }
- case ICAL_CREATED_PROPERTY: {
- struct icaltimetype tt = icalproperty_get_created(prop);
- entry->created_at = icaltime_as_timet(tt);
- break;
- }
-
- default:
- break;
- }
- prop =
- icalcomponent_get_next_property(journal, ICAL_ANY_PROPERTY);
- }
+ entry->component = component;
return 0;
}
// Dynamically create a time string using format
char *
-time_string(time_t time_val, const char *format)
+time_string(icaltimetype t, const char *format)
{
+ time_t time_val = icaltime_to_time_t(t);
struct tm tm_info;
if (!localtime_r(&time_val, &tm_info)) {
return NULL;
@@ -373,8 +313,11 @@ time_string(time_t time_val, const char *format)
// Uses created_at as filename.
// However, in case of collission, set the add 1,2 et.c.
char *
-get_unique_filename(time_t created_at, struct hashmap *entries_map)
+get_unique_filename(journal_entry *entry, struct hashmap *entries_map)
{
+ icalcomponent *c = entry->component;
+ icaltimetype created_at = icalcomponent_get_dtstamp(c);
+
char *filename_time_str = time_string(created_at, date_title_fmt);
if (!filename_time_str) {
LOG("Failed to format date string");
@@ -462,8 +405,8 @@ load_journal_entries()
// Make sure filename, which happens when
// Two entries are created on the same minute
- char *unique_filename = get_unique_filename(
- new_entry->created_at, entries_fuse_key);
+ char *unique_filename =
+ get_unique_filename(new_entry, entries_fuse_key);
if (!unique_filename) {
LOG("Failed to generate filename for %s",
entry->d_name);
@@ -501,8 +444,9 @@ size_t
write_entry_content(char *buf, journal_entry *entry)
{
// Allocate memory for the content
- size_t size =
- asprintf(&buf, "%s\n---\n%s", entry->summary, entry->description);
+ size_t size = asprintf(&buf, "%s\n---\n%s",
+ icalcomponent_get_summary(entry->component),
+ icalcomponent_get_description(entry->component));
if (size == -1) {
LOG("Out of memory");
exit(1);
@@ -513,8 +457,9 @@ write_entry_content(char *buf, journal_entry *entry)
size_t
get_entry_content_size(journal_entry *entry)
{
- size_t size = snprintf(NULL, 0, "%s\n---\n%s", entry->summary,
- entry->description);
+ size_t size = snprintf(NULL, 0, "%s\n---\n%s",
+ icalcomponent_get_summary(entry->component),
+ icalcomponent_get_description(entry->component));
if (size == -1) {
LOG("Out of mem");
exit(1);
@@ -543,7 +488,7 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
ret_code = -ENOENT;
goto unlock_and_return;
}
- stbuf->st_mode = S_IFREG | 0444;
+ stbuf->st_mode = S_IFREG | 0774;
stbuf->st_nlink = 1;
int size = get_entry_content_size(entry);
@@ -551,8 +496,21 @@ 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);
- stbuf->st_mtime = entry->modified_at;
- stbuf->st_ctime = entry->modified_at;
+ icalproperty *last_modified_prop = icalcomponent_get_next_property(
+ entry->component, ICAL_LASTMODIFIED_PROPERTY);
+
+ time_t lastmodified;
+ if (last_modified_prop) {
+ icaltimetype ical_lastmodified =
+ icalproperty_get_lastmodified(last_modified_prop);
+ lastmodified = icaltime_to_time_t(ical_lastmodified);
+ }
+ else {
+ lastmodified = time(NULL);
+ }
+
+ stbuf->st_mtime = lastmodified;
+ stbuf->st_ctime = lastmodified;
stbuf->st_atime = time(NULL);
ret_code = 0;
@@ -611,12 +569,12 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
goto unlock_and_return;
}
- LOG("Got entry %s", entry->summary);
-
// Calculate required length for full content
char *full_content = NULL;
- size_t needed_len = asprintf(&full_content, "%s\n---\n%s",
- entry->summary, entry->description);
+ size_t needed_len =
+ asprintf(&full_content, "%s\n---\n%s",
+ icalcomponent_get_summary(entry->component),
+ icalcomponent_get_description(entry->component));
if (needed_len == -1) {
LOG("Out of memory");
exit(1);
@@ -643,51 +601,12 @@ unlock_and_return:
return ret_code;
}
-char *
-journal_entry_to_ical(const journal_entry *entry)
-{
- char *escaped_summary = escape_text(entry->summary);
- char *escaped_description = escape_text(entry->description);
-
- // Format timestamps
- struct tm created_tm, modified_tm;
- gmtime_r(&entry->created_at, &created_tm);
- gmtime_r(&entry->modified_at, &modified_tm);
-
- char dtstamp[32], lastmod[32];
- strftime(dtstamp, sizeof(dtstamp), "%Y%m%dT%H%M%SZ", &created_tm);
- strftime(lastmod, sizeof(lastmod), "%Y%m%dT%H%M%SZ", &modified_tm);
-
- char *ical = NULL;
-
- asprintf(&ical,
- "BEGIN:VCALENDAR\n"
- "PRODID:caldavfs\n"
- "VERSION:2.0\n"
- "BEGIN:VJOURNAL\n"
- "UID:%s\n"
- "DTSTAMP:%s\n"
- "CREATED:%s\n"
- "LAST-MODIFIED:%s\n"
- "SUMMARY:%s\n"
- "STATUS:FINAL\n"
- "DESCRIPTION:%s\n"
- "END:VJOURNAL\n"
- "END:VCALENDAR\n",
- entry->uid, dtstamp, dtstamp, lastmod, escaped_summary,
- escaped_description);
-
- free(escaped_summary);
- free(escaped_description);
- return ical;
-}
-
size_t
parse_entry_content(journal_entry *entry, const char *buf, size_t size)
{
char *copy = strndup(buf, size);
- char *summary = NULL, *description = NULL;
+ char *description = NULL;
char *sep = strstr(copy, "\n---\n");
// Malformed input
@@ -699,26 +618,27 @@ parse_entry_content(journal_entry *entry, const char *buf, size_t size)
*sep = '\0';
description = sep + 5;
- char *summary_line = strtok(copy, "\n");
- if (!summary_line) {
+ char *summary = strtok(copy, "\n");
+ if (!summary) {
free(copy);
return -EINVAL;
}
- summary = strdup(summary_line);
-
- // Clean up old values
- if (entry->summary != NULL && strcmp(entry->summary, "") != 0) {
- free(entry->summary);
+ icalproperty *prop = icalcomponent_get_first_property(
+ entry->component, ICAL_LASTMODIFIED_PROPERTY);
+ if (!prop) {
+ prop = icalproperty_new_lastmodified(icaltime_null_time());
+ icalcomponent_add_property(entry->component, prop);
}
-
- if (entry->description != NULL && strcmp(entry->description, "") != 0) {
- free(entry->description);
+ else {
+ time_t now = time(NULL);
+ struct icaltimetype now_tt =
+ icaltime_from_timet_with_zone(now, 0, NULL);
+ icalproperty_set_lastmodified(prop, now_tt);
}
- entry->summary = summary;
- entry->description = strdup(description);
- entry->modified_at = time(NULL);
+ icalcomponent_set_summary(entry->component, summary);
+ icalcomponent_set_description(entry->component, description);
free(copy);
return 0;
}
@@ -726,7 +646,7 @@ 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)
{
- char *ical_str = journal_entry_to_ical(entry);
+ char *ical_str = icalcomponent_as_ical_string_r(entry->component);
FILE *f = fopen(filepath, "w");
if (!f) {
@@ -762,7 +682,6 @@ journal_write(const char *path, const char *buf, size_t size, off_t offset,
return -ENOENT;
}
- // Update hashmap with input
LOG("Parsing entry content");
int ret = parse_entry_content(entry, buf, size);
if (ret != 0) {
@@ -788,12 +707,7 @@ copy_journal_entry(const journal_entry *src)
copy->filename = strdup(src->filename);
copy->filename_original = strdup(src->filename_original);
- copy->summary = strdup(src->summary);
- copy->description = strdup(src->description);
- copy->uid = strdup(src->uid);
-
- copy->modified_at = src->modified_at;
- copy->created_at = src->created_at;
+ copy->component = icalcomponent_new_clone(src->component);
return copy;
}
@@ -855,7 +769,10 @@ do_journal_entry_rename(time_t new_created_at, const char *old, const char *new)
journal_entry *new_entry = copy_journal_entry(old_entry);
free(new_entry->filename);
new_entry->filename = strdup(new_basename);
- new_entry->created_at = new_created_at;
+ struct icaltimetype created_at_ical =
+ icaltime_from_timet_with_zone(new_created_at, 0, NULL);
+
+ icalcomponent_set_dtstamp(new_entry->component, created_at_ical);
// hashmap frees old_entry
hashmap_remove(entries_original_key, old_entry->filename_original);
@@ -962,6 +879,37 @@ create_new_unique_ics_uid()
return res;
}
+icalcomponent *
+create_vjournal_entry(time_t created_at)
+{
+ struct icaltimetype created_at_ical =
+ icaltime_from_timet_with_zone(created_at, 0, NULL);
+
+ icalcomponent *calendar = icalcomponent_new_vcalendar();
+
+ icalcomponent_add_property(calendar, icalproperty_new_version("2.0"));
+ icalcomponent_add_property(calendar,
+ icalproperty_new_prodid("-//caldavfs//EN"));
+
+ // Step 2: Create a VJOURNAL entry
+ icalcomponent *journal = icalcomponent_new_vjournal();
+
+ 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(""));
+ 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;
+}
+
int
journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
{
@@ -978,15 +926,8 @@ journal_create(const char *filename, mode_t mode, struct fuse_file_info *info)
pthread_mutex_unlock(&entries_mutex);
return -EINVAL;
}
-
journal_entry *new_entry = xcalloc(1, sizeof(journal_entry));
- new_entry->filename = strdup(new_basename);
- new_entry->created_at = created_at;
- new_entry->modified_at = created_at;
- new_entry->summary = "";
- new_entry->description = "";
- new_entry->uid = create_new_unique_ics_uid();
- asprintf(&new_entry->filename_original, "%s.ics", new_entry->uid);
+ create_vjournal_entry(created_at);
hashmap_insert(entries_original_key, new_entry->filename_original,
new_entry);
@@ -1038,11 +979,6 @@ update_fuse_entry_from_original(const char *filename)
new_entry->filename_original =
strndup(entry->filename_original, strlen(entry->filename_original));
- LOG("Updating file path %s, new entry %s %s. New summary: %s , new "
- "desc: %s ",
- filename, new_entry->filename, new_entry->filename_original,
- new_entry->summary, new_entry->description);
-
hashmap_insert(entries_original_key, new_entry->filename_original,
new_entry);
hashmap_insert(entries_fuse_key, new_entry->filename, new_entry);