agendafs

A filesystem for your calendar.

git clone git://mccd.space/agendafs
commit cd24d849041a155603fb3864013b9c7871f3f80f
parent 698eb179ffe9bd84766044e28267970695e9765c
Author: Marc Coquand <marc@coquand.email>
Date:   Mon, 28 Jul 2025 12:55:17 +0200

Add option to set default file extension

Implements: https://todo.sr.ht/~marcc/agendafs/30

Diffstat:
Magendafs.8.scd | 5++++-
Mfuse_node.c | 2+-
Mfuse_node_store.c | 23+++++++++++++++++++++++
Mfuse_node_store.h | 6++++++
Mmain.c | 35++++++++++++++++++++++++++++++-----
5 files changed, 64 insertions(+), 7 deletions(-)
diff --git a/agendafs.8.scd b/agendafs.8.scd
@@ -21,6 +21,9 @@ are agendafs specific options:
 *vdir=*_vdirpath_
 	Use the specified vdir storage path as mountpoint. Currently
 	only one calendar is supported. Required.
+*ext=*_fileextension_
+	When files are created outside of agendafs, a file extension
+	is set. Defaults to txt.
 
 # XATTRIBUTES
 
@@ -39,7 +42,7 @@ usage. Supported extended attributes are:
 Mount vdir storage with agendafs to your home journal directory:
 
 	```
-	$ mount.agendafs -o vdir=$HOME/.calendars/vjournal-calendar \\
+	$ mount.agendafs -o ext=md -o vdir=$HOME/.calendars/vjournal-calendar \\
 	  $HOME/journal
 	```
 
diff --git a/fuse_node.c b/fuse_node.c
@@ -212,7 +212,7 @@ parse_ics_to_agenda_entry(memory_region *mreg, const char *vdir_filepath)
 		const char *extension =
 		    icalcomponent_get_file_extension(component);
 		if (!extension) {
-			extension = "txt";
+			extension = get_default_file_extension();
 		}
 
 		rasprintf(mreg, &filename, "%s.%s", summary, extension);
diff --git a/fuse_node_store.c b/fuse_node_store.c
@@ -8,6 +8,8 @@
 #include <string.h>
 char VDIR[256];
 
+char FILE_EXTENSION[256] = "txt";
+
 // A structure of the current root
 struct tree_node *fuse_root = NULL;
 // ics entries, keys are the filename as stored in ICS_DIR
@@ -21,6 +23,27 @@ set_vdir(const char *expanded_path)
 	strlcpy(VDIR, expanded_path, sizeof(VDIR));
 }
 
+const char *
+get_default_file_extension()
+{
+	return FILE_EXTENSION;
+}
+
+int
+set_file_extension(const char *extension)
+{
+	if (extension[0] == '.') {
+		return -1;
+	}
+
+	if (strlen(extension) > 256) {
+		return -2;
+	}
+
+	strlcpy(FILE_EXTENSION, extension, sizeof(FILE_EXTENSION));
+	return 0;
+}
+
 int
 set_node_filename(struct tree_node *node, const char *filename)
 {
diff --git a/fuse_node_store.h b/fuse_node_store.h
@@ -53,4 +53,10 @@ add_fuse_child(struct tree_node *parent, struct tree_node *child);
 size_t
 move_fuse_node(struct tree_node *new_parent, struct tree_node *child);
 
+const char *
+get_default_file_extension();
+
+int
+set_file_extension(const char *);
+
 #endif // fuse_node_store_h_INCLUDED
diff --git a/main.c b/main.c
@@ -648,6 +648,7 @@ fuse_utimens(const char *path, const struct timespec tv[2],
 
 struct agendafs_config {
 	char *ics_directory;
+	char *default_file_extension;
 };
 enum {
 	KEY_HELP,
@@ -657,9 +658,13 @@ enum {
 #define CUSTOMFS_OPT(t, p, v) {t, offsetof(struct agendafs_config, p), v}
 
 static struct fuse_opt agendafs_opts[] = {
-    CUSTOMFS_OPT("vdir=%s", ics_directory, 0), FUSE_OPT_KEY("-V", KEY_VERSION),
-    FUSE_OPT_KEY("--version", KEY_VERSION),    FUSE_OPT_KEY("-h", KEY_HELP),
-    FUSE_OPT_KEY("--help", KEY_HELP),	       FUSE_OPT_END};
+    CUSTOMFS_OPT("ext=%s", default_file_extension, 0),
+    CUSTOMFS_OPT("vdir=%s", ics_directory, 0),
+    FUSE_OPT_KEY("-V", KEY_VERSION),
+    FUSE_OPT_KEY("--version", KEY_VERSION),
+    FUSE_OPT_KEY("-h", KEY_HELP),
+    FUSE_OPT_KEY("--help", KEY_HELP),
+    FUSE_OPT_END};
 
 size_t
 load_agendafs_environment(char *vdir_env)
@@ -740,7 +745,7 @@ main(int argc, char *argv[])
 
 	fuse_opt_parse(&args, &conf, agendafs_opts, agendafs_opt_proc);
 
-	if (conf.ics_directory == NULL) {
+	if (!conf.ics_directory) {
 		fprintf(stderr,
 			"usage: %s [options] mountpoint\n"
 			"\n"
@@ -752,6 +757,25 @@ main(int argc, char *argv[])
 		exit(1);
 	}
 
+	if (conf.default_file_extension) {
+		LOG("DEFAULT FILE EXTENSION SET");
+		int res = set_file_extension(conf.default_file_extension);
+		if (res == -1) {
+			fprintf(stderr,
+				"File extension should be without dot\n");
+			exit(1);
+		}
+
+		if (res == -2) {
+			fprintf(stderr, "File extension can not be more than "
+					"256 characters\n");
+			exit(1);
+		}
+	}
+	else {
+		LOG("DEFAULT FILE EXTENSION NOT SET. DEFAULTING TO .txt");
+	}
+
 #ifdef DEBUG
 	fuse_opt_add_arg(&args, "-f");
 #endif
@@ -763,13 +787,14 @@ main(int argc, char *argv[])
 
 	LOG("LOADED ICS DIR");
 
+	load_root_node_tree();
+
 	if (pthread_create(&watcher_thread, NULL, watch_vdir_changes, NULL) !=
 	    0) {
 		perror("Failed to create inotify watcher thread");
 		return 1;
 	}
 
-	load_root_node_tree();
 	int ret = fuse_main(args.argc, args.argv, &fuse_oper, NULL);
 	LOG("Cleaning up");