agendafs
A filesystem for your calendar.
git clone git://mccd.space/agendafscommit 5fbe24b3c74068345e4a69342db653b5bdda759a
parent 4ab4a8d3921063c124b2c40179f3139d5e76d2d4
Author: Marc Coquand <marc@coquand.email>
Date: Sat, 14 Jun 2025 23:20:15 +0100
Add rename with file name being the summary
Diffstat:
| M | hashmap.c | | | 127 | +++++++++++++++++++++++++++++++------------------------------------------------ |
| M | main.c | | | 159 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------- |
2 files changed, 165 insertions(+), 121 deletions(-)
diff --git a/hashmap.c b/hashmap.c
@@ -9,7 +9,8 @@
#ifdef HM_DEBUG
#include <stdarg.h>
-static void debug_print(char *fmt, ...)
+static void
+debug_print(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
@@ -18,10 +19,14 @@ static void debug_print(char *fmt, ...)
}
#else
/* compiler should just optimize this away */
-static void debug_print() {}
+static void
+debug_print()
+{
+}
#endif
-static int hm_check_mem(void *ptr)
+static int
+hm_check_mem(void *ptr)
{
if (ptr != NULL)
return 0;
@@ -35,12 +40,14 @@ static int hm_check_mem(void *ptr)
}
/* hash a single byte */
-static uint32_t fnv1a(unsigned char byte, uint32_t hash)
+static uint32_t
+fnv1a(unsigned char byte, uint32_t hash)
{
return (byte ^ hash) * HM_FNV_PRIME;
}
-static uint32_t fnv1a_hash(const char *str)
+static uint32_t
+fnv1a_hash(const char *str)
{
uint32_t hash = HM_FNV_SEED;
while (*str)
@@ -49,7 +56,8 @@ static uint32_t fnv1a_hash(const char *str)
}
/* will duplicate key */
-static struct bucket *new_bucket(const char *key, void *val)
+static struct bucket *
+new_bucket(const char *key, void *val)
{
if (key == NULL)
return NULL;
@@ -76,7 +84,8 @@ static struct bucket *new_bucket(const char *key, void *val)
return b;
}
-struct hashmap *hashmap_new(void (*val_free_func)(void *))
+struct hashmap *
+hashmap_new(void (*val_free_func)(void *))
{
/* Any smaller and we'd pretty much instantly have to resize */
size_t capacity = HM_DEFAULT_HASHMAP_SIZE;
@@ -97,7 +106,8 @@ struct hashmap *hashmap_new(void (*val_free_func)(void *))
return hm;
}
-static void free_bucket(struct bucket *b, void (*val_free_func)(void *))
+static void
+free_bucket(struct bucket *b, void (*val_free_func)(void *))
{
if (b == NULL)
return;
@@ -114,7 +124,8 @@ static void free_bucket(struct bucket *b, void (*val_free_func)(void *))
}
}
-void hashmap_free(struct hashmap *hm)
+void
+hashmap_free(struct hashmap *hm)
{
if (hm == NULL)
return;
@@ -127,7 +138,8 @@ void hashmap_free(struct hashmap *hm)
free(hm);
}
-int hashmap_resize(struct hashmap *hm)
+int
+hashmap_resize(struct hashmap *hm)
{
if (hm == NULL)
return -1;
@@ -183,7 +195,8 @@ int hashmap_resize(struct hashmap *hm)
return 0;
}
-int hashmap_insert(struct hashmap *hm, const char *key, void *val)
+int
+hashmap_insert(struct hashmap *hm, const char *key, void *val)
{
if (hm == NULL || key == NULL)
return 1;
@@ -239,7 +252,8 @@ int hashmap_insert(struct hashmap *hm, const char *key, void *val)
return 0;
}
-void *hashmap_get(struct hashmap *hm, const char *key)
+void *
+hashmap_get(struct hashmap *hm, const char *key)
{
if (hm == NULL || key == NULL)
return NULL;
@@ -290,83 +304,37 @@ void *hashmap_get(struct hashmap *hm, const char *key)
* us to iterate through the hashmap to find the item if for some
* reason the hash gives an incorrect index
*/
-static int _hashmap_remove(struct hashmap *hm, const char *key, uint32_t hash,
- size_t idx)
+static int
+_hashmap_remove(struct hashmap *hm, const char *key, uint32_t hash, size_t idx)
{
if (hm == NULL || key == NULL)
return 1;
- // uint32_t hash = fnv1a_hash(key);
- // size_t idx = hash % (uint32_t)(hm->capacity);
-
- debug_print("removing object with key %s which hashes to %" PRIu32
- ". Supplied index to search is %ld\n",
- key, hash, idx);
-
struct bucket *target = hm->buckets[idx];
- if (target == NULL)
- return 1;
+ struct bucket *prev = NULL;
- if (target->hash == hash && strcmp(target->key, key) == 0) {
- /* bucket with single item */
- if (target->next == NULL) {
- debug_print(
- "simple removal of bucket with single node\n");
- free_bucket(hm->buckets[idx], hm->val_free_func);
- hm->buckets[idx] = NULL;
+ while (target != NULL) {
+ if (target->hash == hash && strcmp(target->key, key) == 0) {
+ if (prev == NULL) {
+ // Removing first node in bucket chain
+ hm->buckets[idx] = target->next;
+ }
+ else {
+ prev->next = target->next;
+ }
+ target->next = NULL;
+ free_bucket(target, hm->val_free_func);
hm->n_buckets--;
return 0;
}
-
- /* bucket where first item is to be removed,
- * but there are other items in the linked list */
- debug_print("removing first node and preserving the rest of "
- "the linked list\n");
-
- struct bucket *next = hm->buckets[idx]->next;
- hm->buckets[idx] = next;
- target->next = NULL;
- free_bucket(target, hm->val_free_func);
- return 0;
- }
-
- /* the bucket to be removed is not the first
- * bucket in the linked list */
- debug_print(
- "bucket to be removed is within the linked list - searching...\n");
- if (target->next == NULL)
- return 1;
-
- target = target->next;
-
- struct bucket *curr = target;
- struct bucket *prev = NULL;
-
- while (target != NULL) {
- curr = target;
+ prev = target;
target = target->next;
-
- if (curr->hash == hash && strcmp(curr->key, key) != 0) {
- prev = curr;
- continue;
- }
-
- if (prev == NULL) {
- debug_print("this should never happen...\n");
- return 1;
- }
-
- prev->next = curr->next;
- curr->next = NULL;
- free_bucket(curr, hm->val_free_func);
-
- return 0;
}
-
return 1;
}
-int hashmap_remove(struct hashmap *hm, const char *key)
+int
+hashmap_remove(struct hashmap *hm, const char *key)
{
if (hm == NULL || key == NULL)
return 1;
@@ -401,7 +369,8 @@ int hashmap_remove(struct hashmap *hm, const char *key)
}
/* only works for hashmap with strings as values */
-void hashmap_print(struct hashmap *hm)
+void
+hashmap_print(struct hashmap *hm)
{
if (hm == NULL) {
printf("NULL\n");
@@ -430,7 +399,8 @@ void hashmap_print(struct hashmap *hm)
}
}
-char **hashmap_get_keys(struct hashmap *hm, size_t *n_keys)
+char **
+hashmap_get_keys(struct hashmap *hm, size_t *n_keys)
{
if (!hm || !n_keys)
return NULL;
@@ -452,7 +422,8 @@ char **hashmap_get_keys(struct hashmap *hm, size_t *n_keys)
return keys;
}
-void hashmap_free_keys(char **keys, size_t n_keys)
+void
+hashmap_free_keys(char **keys, size_t n_keys)
{
if (!keys)
return;
diff --git a/main.c b/main.c
@@ -33,10 +33,19 @@ time_t INVALID_TIME = (time_t)-1;
icaltimezone *local_ical_time;
-#define ENOTJOURNAL 1001
-
+// TODO: Move this to a file structure to a separate module, refactor all
+// operations to use the module exposed files
+// Such as
+// - rename
+// - remove
+// - get filename
+// et.c.
typedef struct {
+ // filename is full path relative to fuse directory
+ // I.E. hello world.txt
char *filename;
+ // filename_original is full path relative to fuse directory
+ // I.E. 910319208nrao19p.ics
char *filename_original;
icalcomponent *component;
} journal_entry;
@@ -86,6 +95,14 @@ free_journal_entry(void *val)
free(entry);
}
+char *
+get_filename(const char *full_path)
+{
+ char *base_name = strrchr(full_path, '/');
+ if (!base_name)
+ return (char *)full_path;
+ return base_name + 1;
+}
int
compile_regexes(regex_t *regex_fuse_file)
{
@@ -121,7 +138,7 @@ get_entry_from_fuse_path(const char *path)
return NULL;
}
- LOG("Looking up key: '%s'\n", key);
+ LOG("Looking up key: '%s'", key);
journal_entry *entry = hashmap_get(entries_fuse, key);
if (!entry) {
@@ -129,8 +146,8 @@ get_entry_from_fuse_path(const char *path)
return NULL;
}
- LOG("Entry found: filename = '%s', original = '%s'\n", entry->filename,
- entry->filename_original);
+ LOG("Entry found: filename = '%s', original = '%s', path = '%s'",
+ entry->filename, entry->filename_original, path);
return entry;
}
@@ -166,7 +183,7 @@ parse_ics_file(const char *filename)
// We assume that there is only one entry in the file
int
-parse_ics_to_journal_entry(const char *filename, journal_entry *entry)
+parse_ics_to_journal_entry_component(const char *filename, journal_entry *entry)
{
icalcomponent *component = parse_ics_file(filename);
if (component == NULL) {
@@ -199,7 +216,7 @@ time_string(icaltimetype t, const char *format)
// Returns -1 on failure, 0 on success
int
-load_journal_entry(char *filename, journal_entry *entry)
+load_journal_entry_from_ics_file(char *filename, journal_entry *entry)
{
char *filepath = NULL;
if (asprintf(&filepath, "%s/%s", ICS_DIR, filename) == -1) {
@@ -213,21 +230,21 @@ load_journal_entry(char *filename, journal_entry *entry)
return -1;
}
- if (parse_ics_to_journal_entry(filepath, entry) != 0) {
+ if (parse_ics_to_journal_entry_component(filepath, entry) != 0) {
LOG("Failed to parse entry: %s", filename);
free(filepath);
return -1;
}
- LOG("Parsed %s", entry->filename_original);
-
// Make sure filename, which happens when
// Two entries are created on the same minute
+ // TODO: Allow custom fileformats
if (asprintf(&entry->filename, "%s.txt",
- icalcomponent_get_uid(entry->component)) == -1) {
+ icalcomponent_get_summary(entry->component)) == -1) {
LOG("Failed to write filename");
return -1;
};
+ LOG("Parsed %s to file %s", entry->filename_original, entry->filename);
free(filepath);
return 0;
@@ -241,7 +258,15 @@ void
load_journal_entries()
{
pthread_mutex_lock(&entries_mutex);
-
+ LOG("Allocating journal entries");
+ if (entries_original_ics) {
+ hashmap_free(entries_original_ics);
+ entries_original_ics = NULL;
+ }
+ if (entries_fuse) {
+ hashmap_free(entries_fuse);
+ entries_fuse = NULL;
+ }
entries_original_ics = hashmap_new(NULL);
entries_fuse = hashmap_new(free_journal_entry);
@@ -260,7 +285,8 @@ load_journal_entries()
journal_entry *new_entry =
xcalloc(1, sizeof(journal_entry));
- if (load_journal_entry(entry->d_name, new_entry) != 0) {
+ if (load_journal_entry_from_ics_file(entry->d_name,
+ new_entry) != 0) {
LOG("FAILED TO LOAD JOURNAL ENTRY");
free_journal_entry(new_entry);
continue;
@@ -285,8 +311,7 @@ size_t
write_entry_content(char *buf, journal_entry *entry)
{
// Allocate memory for the content
- size_t size = asprintf(&buf, "%s\n---\n%s",
- icalcomponent_get_summary(entry->component),
+ size_t size = asprintf(&buf, "%s",
icalcomponent_get_description(entry->component));
if (size == -1) {
LOG("Out of memory");
@@ -298,8 +323,7 @@ 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",
- icalcomponent_get_summary(entry->component),
+ size_t size = snprintf(NULL, 0, "%s",
icalcomponent_get_description(entry->component));
if (size == -1) {
LOG("Out of mem");
@@ -360,7 +384,9 @@ build_today_hashmap()
LOG("SUMMARY IS NULL");
continue;
}
- char *filename = strdup(summary);
+ char *filename;
+
+ asprintf(&filename, "%s", summary);
if (filename == NULL) {
LOG("Could not get filename");
@@ -421,7 +447,8 @@ build_notes_hashmap()
LOG("SUMMARY IS NULL");
continue;
}
- char *filename = strdup(summary);
+ char *filename;
+ asprintf(&filename, "%s", summary);
if (filename == NULL) {
LOG("Could not get filename");
@@ -458,20 +485,17 @@ journal_getattr(const char *path, struct stat *stbuf, struct fuse_file_info *fi)
stbuf->st_nlink = 2;
goto unlock_and_return;
}
-
- if (strcmp(path, "/entries") == 0) {
+ else if (strcmp(path, "/entries") == 0) {
stbuf->st_mode = S_IFDIR | 0444;
stbuf->st_nlink = 2;
goto unlock_and_return;
}
-
- if (strcmp(path, "/today") == 0) {
+ else if (strcmp(path, "/today") == 0) {
stbuf->st_mode = S_IFDIR | 0444;
stbuf->st_nlink = 2;
goto unlock_and_return;
}
-
- if (strcmp(path, "/notes") == 0) {
+ else if (strcmp(path, "/notes") == 0) {
stbuf->st_mode = S_IFDIR | 0444;
stbuf->st_nlink = 2;
goto unlock_and_return;
@@ -693,8 +717,7 @@ journal_read(const char *path, char *buf, size_t size, off_t offset,
// Calculate required length for full content
char *full_content = NULL;
- size_t needed_len =
- asprintf(&full_content, "%s\n---\n%s", summary, description);
+ size_t needed_len = asprintf(&full_content, "%s", description);
if (needed_len == -1) {
LOG("Out of memory");
exit(1);
@@ -859,9 +882,63 @@ timestamp_from_filename(const char *filename)
}
int
-do_journal_entry_rename(time_t new_created_at, const char *old, const char *new)
+do_journal_entry_rename(const char *old, const char *new)
{
- return -EIO;
+ char *new_copy = strdup(new);
+ if (!new_copy)
+ return -ENOMEM;
+ char *new_filename = get_filename(new_copy);
+
+ char *old_filename = get_filename(old);
+ if (!old_filename)
+ return -EINVAL;
+
+ char *new_summary = strdup(new_filename);
+
+ // Remove everything after extension
+ // TODO: Store the extension as a separate field in the ics file
+ char *dot = strrchr(new_summary, '.');
+ if (dot) {
+ *dot = '\0';
+ }
+ LOG("Summary is now %s", new_summary);
+
+ journal_entry *old_entry = get_entry_from_fuse_path(old);
+ if (!old_entry) {
+ LOG("Entry not found '%s'", old);
+ free(new_copy);
+ free(new_summary);
+ return -EIO;
+ }
+
+ journal_entry *new_entry = copy_journal_entry(old_entry);
+ if (!new_entry) {
+ free(new_copy);
+ free(new_summary);
+ return -EIO;
+ }
+
+ icalcomponent_set_summary(new_entry->component, new_summary);
+ // TODO: Filename must be unique
+ asprintf(&new_entry->filename, "%s", new_filename);
+ LOG("New filename is now %s", new_filename);
+
+ LOG("Removed original entry from fuse");
+
+ LOG("Inserting %s", new_entry->filename_original);
+ hashmap_insert(entries_original_ics, new_entry->filename_original,
+ new_entry);
+
+ hashmap_insert(entries_fuse, new_entry->filename, new_entry);
+
+ // Insert original ics first to avoid use after free
+ // Insert frees older entry
+ hashmap_remove(entries_fuse, old_entry->filename);
+
+ write_entry_to_ical_file(new_entry);
+ free(new_copy);
+
+ return 0;
}
// Aka remove or delete
@@ -916,19 +993,14 @@ journal_rename(const char *old, const char *new, unsigned int flags)
{
pthread_mutex_lock(&entries_mutex);
LOG("RENAME %s -> %s", old, new);
+ if (strncmp(old, "/entries/", strlen("/entries/")) != 0 ||
+ strncmp(new, "/entries/", strlen("/entries/")) != 0) {
- // Strip path, validate just the new basename
- const char *new_basename = strrchr(new, '/');
- new_basename++;
- time_t new_created_at = timestamp_from_filename(new_basename);
-
- if (new_created_at == INVALID_TIME) {
- LOG("Invalid filename format: %s", new_basename);
pthread_mutex_unlock(&entries_mutex);
- return -EINVAL;
+ LOG("Invalid file names");
+ return -ENOTSUP;
}
-
- int res = do_journal_entry_rename(new_created_at, old, new);
+ int res = do_journal_entry_rename(old, new);
pthread_mutex_unlock(&entries_mutex);
return res;
@@ -950,7 +1022,7 @@ create_new_unique_ics_uid()
}
icalcomponent *
-create_vjournal_entry(char *uid)
+create_vjournal_entry(char *summary)
{
icalcomponent *calendar = icalcomponent_new_vcalendar();
@@ -961,13 +1033,14 @@ create_vjournal_entry(char *uid)
// Step 2: Create a VJOURNAL entry
icalcomponent *journal = icalcomponent_new_vjournal();
+ char *id = create_new_unique_ics_uid();
- icalcomponent_add_property(journal, icalproperty_new_uid(uid));
+ icalcomponent_add_property(journal, icalproperty_new_uid(id));
icalcomponent_add_property(
journal, icalproperty_new_dtstamp(icaltime_current_time_with_zone(
icaltimezone_get_utc_timezone())));
- icalcomponent_add_property(journal, icalproperty_new_summary(""));
+ icalcomponent_add_property(journal, icalproperty_new_summary(summary));
icalcomponent_add_property(journal, icalproperty_new_description(""));
icalcomponent_add_component(calendar, journal);
@@ -1044,7 +1117,7 @@ update_or_create_fuse_entry_from_original(const char *filename)
char *base_name = strrchr(filename, '/');
journal_entry *new_entry = xcalloc(1, sizeof(journal_entry));
- if (load_journal_entry(base_name, new_entry) != 0) {
+ if (load_journal_entry_from_ics_file(base_name, new_entry) != 0) {
// Parsing failed, clean up and maybe remove old entry
// if any
LOG("PARSING FAILED");