diff --git a/src/get_handler.c b/src/get_handler.c
new file mode 100644
index 0000000..86ee33c
--- /dev/null
+++ b/src/get_handler.c
@@ -0,0 +1,6 @@
+static enum MHD_Result handle_get(struct MHD_Connection *connection, SV url, SV base_path, SV username) {
+ (void)url;
+ (void)username;
+ (void)base_path;
+ return send_response(connection, 200, sv("TODO: Get\n"), false);
+}
diff --git a/src/id_store.c b/src/id_store.c
new file mode 100644
index 0000000..de58e58
--- /dev/null
+++ b/src/id_store.c
@@ -0,0 +1,35 @@
+
+typedef struct {
+ Noh_String path;
+ Noh_String id;
+} Id;
+
+typedef struct {
+ Id *elems;
+ size_t count;
+ size_t capacity;
+} Id_Store;
+
+Id_Store id_store = {0};
+
+SV get_id(SV path) {
+ for (size_t i = 0; i < id_store.count; i++) {
+ if (noh_sv_eq(path, noh_sv_from_string(id_store.elems[i].path)))
+ return noh_sv_from_string(id_store.elems[i].id);
+ }
+
+ uuid_t uuid;
+ uuid_generate(uuid);
+ char uuid_str[36];
+ uuid_unparse(uuid, uuid_str);
+
+ Noh_String new_id = {0};
+ noh_string_append_cstr(&new_id, "urn:uuid:");
+ noh_string_append_cstr(&new_id, uuid_str);
+
+ Noh_String stored_path = {0};
+ noh_string_append_sv(&stored_path, path);
+
+ noh_da_append(&id_store, ((Id){ .path = stored_path, .id = new_id }));
+ return noh_sv_from_string(new_id);
+}
diff --git a/src/library.c b/src/library.c
new file mode 100644
index 0000000..ca718a9
--- /dev/null
+++ b/src/library.c
@@ -0,0 +1,93 @@
+typedef struct {
+ bool is_dir;
+ SV name;
+ SV full_path;
+ struct tm modified_at;
+} Library_Entry;
+
+typedef struct {
+ Library_Entry *elems;
+ size_t count;
+ size_t capacity;
+} Library_Entries;
+
+/*
+ * Helper to format an entry's last modified date into a temporary buffer.
+ * The same buffer will be reused the next time this function is called.
+ */
+static char *format_time(Library_Entry entry) {
+ static char time_buffer[32];
+ strftime((char*)&time_buffer, 32, "%Y-%m-%dT%H:%M:%S.0000000Z", &entry.modified_at);
+ return (char*)time_buffer;
+}
+
+static int compare_library_entries(const void *a, const void *b) {
+ Library_Entry lea = *(Library_Entry*)a;
+ Library_Entry leb = *(Library_Entry*)b;
+
+ if (lea.is_dir && !leb.is_dir) return -1;
+ if (!lea.is_dir && leb.is_dir) return 1;
+
+ return noh_sv_compare(lea.name, leb.name);
+}
+
+static void set_modified_time(Library_Entry *entry) {
+ struct stat attr;
+ if (stat(noh_arena_sprintf(&temp, Nsv_Fmt, entry->full_path), &attr) == 0) {
+ struct tm *timeinfo = localtime(&attr.st_mtime);
+ entry->modified_at = *timeinfo;
+ } else {
+ entry->modified_at = (struct tm){0};
+ }
+}
+
+#define build_path(url, first, ...) build_path_((url), (first), __VA_ARGS__, NULL)
+static Noh_String build_path_(bool url, SV *first, ...) {
+ va_list args;
+ SV *elem_ = first;
+ va_start(args, first);
+ bool start = true;
+ Noh_String result = {0};
+ if (url) noh_string_append_cstr(&result, "/");
+
+ while (elem_ != NULL) {
+ SV elem = *elem_;
+ while (elem.count > 0) {
+ SV component = noh_sv_chop_by_delim(&elem, '/');
+ if (noh_sv_eq(component, sv(".."))) continue;
+
+ if (component.count > 0) {
+ if (start) start = false; else noh_string_append_cstr(&result, "/");
+ noh_string_append_sv(&result, component);
+ }
+ }
+
+ elem_ = va_arg(args, SV *);
+ }
+ va_end(args);
+
+ return result;
+}
+
+/**
+ * Creates a Library entry from a dirent structure.
+ */
+static bool create_entry(struct dirent *dir_entry, const char *parent_path, Library_Entry *result) {
+ SV name = sv(dir_entry->d_name);
+ SV parent_path_sv = sv(parent_path);
+ Noh_String full_path = build_path(false, &parent_path_sv, &name);
+ int type = dir_entry->d_type;
+
+ // Check if the entry is relevant.
+ if (type != DT_DIR && type != DT_REG) return false;
+ if (noh_sv_eq(name, sv(".")) || noh_sv_eq(name, sv(".."))) return false;
+ if (type == DT_REG && !noh_sv_ends_with_ci(name, sv(".epub"))) return false;
+
+ // Set entry data.
+ result->is_dir = type == DT_DIR;
+ result->full_path = noh_sv_from_string(full_path);
+ result->name = noh_sv_copy_cstr(&temp, dir_entry->d_name);
+ set_modified_time(result);
+
+ return true;
+}
diff --git a/src/main.c b/src/main.c
index 2fe7f9d..0d7aa92 100644
--- a/src/main.c
+++ b/src/main.c
@@ -16,39 +16,13 @@ This will ignore any auth headers and always log in with username demo.
Noh_Arena temp;
-typedef struct {
- Noh_String path;
- Noh_String id;
-} Id;
+#include "id_store.c"
+#include "library.c"
typedef struct {
- Id *elems;
- size_t count;
- size_t capacity;
-} Id_Store;
-
-Id_Store id_store = {0};
-
-static SV get_id(SV path) {
- for (size_t i = 0; i < id_store.count; i++) {
- if (noh_sv_eq(path, noh_sv_from_string(id_store.elems[i].path))) return noh_sv_from_string(id_store.elems[i].id);
- }
-
- uuid_t uuid;
- uuid_generate(uuid);
- char uuid_str[36];
- uuid_unparse(uuid, uuid_str);
-
- Noh_String new_id = {0};
- noh_string_append_cstr(&new_id, "urn:uuid:");
- noh_string_append_cstr(&new_id, uuid_str);
-
- Noh_String stored_path = {0};
- noh_string_append_sv(&stored_path, path);
-
- noh_da_append(&id_store, ((Id){ .path = stored_path, .id = new_id }));
- return noh_sv_from_string(new_id);
-}
+ SV base_path;
+ SV library_name;
+} Settings;
static enum MHD_Result send_response(struct MHD_Connection *connection, int status, SV message, bool xml) {
struct MHD_Response *response = MHD_create_response_from_buffer(
@@ -62,212 +36,8 @@ static enum MHD_Result send_response(struct MHD_Connection *connection, int stat
return ret;
}
-typedef struct {
- bool is_dir;
- SV name;
- SV full_path;
- struct tm modified_at;
-} Library_Entry;
-
-typedef struct {
- Library_Entry *elems;
- size_t count;
- size_t capacity;
-} Library_Entries;
-
-int compare_library_entries(const void *a, const void *b) {
- Library_Entry lea = *(Library_Entry*)a;
- Library_Entry leb = *(Library_Entry*)b;
-
- if (lea.is_dir && !leb.is_dir) return -1;
- if (!lea.is_dir && leb.is_dir) return 1;
-
- return noh_sv_compare(lea.name, leb.name);
-}
-
-#define build_path(url, first, ...) build_path_((url), (first), __VA_ARGS__, NULL)
-static Noh_String build_path_(bool url, SV *first, ...) {
- va_list args;
- SV *elem_ = first;
- va_start(args, first);
- bool start = true;
- Noh_String result = {0};
- if (url) noh_string_append_cstr(&result, "/");
-
- while (elem_ != NULL) {
- SV elem = *elem_;
- while (elem.count > 0) {
- SV component = noh_sv_chop_by_delim(&elem, '/');
- if (noh_sv_eq(component, sv(".."))) continue;
-
- if (component.count > 0) {
- if (start) start = false; else noh_string_append_cstr(&result, "/");
- noh_string_append_sv(&result, component);
- }
- }
-
- elem_ = va_arg(args, SV *);
- }
- va_end(args);
-
- return result;
-}
-
-static SV path_to_name(SV path) {
- while (true) {
- SV component = noh_sv_chop_by_delim(&path, '/');
- if (path.count == 0) return component;
- }
-}
-
-static void set_modified_time(Library_Entry *entry) {
- struct stat attr;
- if (stat(noh_arena_sprintf(&temp, Nsv_Fmt, entry->full_path), &attr) == 0) {
- struct tm *timeinfo = localtime(&attr.st_mtime);
- entry->modified_at = *timeinfo;
- } else {
- entry->modified_at = (struct tm){0};
- }
-}
-
-/**
- * Creates a Library entry from a dirent structure.
- */
-static bool create_entry(struct dirent *dir_entry, const char *parent_path, Library_Entry *result) {
- SV name = sv(dir_entry->d_name);
- SV parent_path_sv = sv(parent_path);
- Noh_String full_path = build_path(false, &parent_path_sv, &name);
- int type = dir_entry->d_type;
-
- // Check if the entry is relevant.
- if (type != DT_DIR && type != DT_REG) return false;
- if (noh_sv_eq(name, sv(".")) || noh_sv_eq(name, sv(".."))) return false;
- if (type == DT_REG && !noh_sv_ends_with_ci(name, sv(".epub"))) return false;
-
- // Set entry data.
- result->is_dir = type == DT_DIR;
- result->full_path = noh_sv_from_string(full_path);
- result->name = noh_sv_copy_cstr(&temp, dir_entry->d_name);
- set_modified_time(result);
-
- return true;
-}
-
-/*
- * Helper to format an entry's last modified date into a temporary buffer.
- * The same buffer will be reused the next time this function is called.
- */
-static char *format_time(Library_Entry entry) {
- static char time_buffer[32];
- strftime((char*)&time_buffer, 32, "%Y-%m-%dT%H:%M:%S.0000000Z", &entry.modified_at);
- return (char*)time_buffer;
-}
-
-static void append_entry(Noh_String *response, SV url, Library_Entry entry) {
- noh_string_append_cstr(response, "");
- noh_string_append_cstr(response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", Nsv_Arg(get_id(entry.full_path))));
- noh_string_append_cstr(response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", entry.name));
- noh_string_append_cstr(response, noh_arena_sprintf(&temp, "%s", format_time(entry)));
- if (entry.is_dir) {
- SV opds = sv("opds");
- Noh_String entry_url = build_path(true, &opds, &url, &entry.name);
- noh_string_append_cstr(response, noh_arena_sprintf(&temp, "", Nsv_Arg(entry_url)));
- noh_string_free(&entry_url);
- } else {
- SV get = sv("get");
- Noh_String entry_url = build_path(true, &get, &url, &entry.name);
- noh_string_append_cstr(response, noh_arena_sprintf(&temp, "", Nsv_Arg(entry_url)));
- noh_string_free(&entry_url);
- }
- noh_string_append_cstr(response, "");
-}
-
-static Noh_String create_feed_response(Library_Entry self_path, SV url, Library_Entries list) {
- (void)self_path;
- Noh_String response = {0};
-
- noh_string_append_cstr(&response, "");
- noh_string_append_cstr(&response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", Nsv_Arg(get_id(self_path.full_path))));
- noh_string_append_cstr(&response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", self_path.name));
- noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "%s", format_time(self_path)));
-
- SV opds = sv("opds");
- Noh_String self_url = build_path(true, &opds, &url);
- noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "", Nsv_Arg(self_url)));
- noh_string_free(&self_url);
-
- noh_string_append_cstr(&response, "");
-
- for (size_t i = 0; i < list.count; i++) append_entry(&response, url, list.elems[i]);
- noh_string_append_cstr(&response, "");
-
- return response;
-}
-
-typedef struct {
- SV base_path;
- SV library_name;
-} Settings;
-
-static enum MHD_Result handle_opds(struct MHD_Connection *connection, SV url, Settings settings, SV username) {
- noh_arena_save(&temp);
- Noh_String fs_path = build_path(false, &settings.base_path, &username, &url);
- Noh_String fs_path_cstr = fs_path;
- noh_string_append_null(&fs_path_cstr);
-
- Library_Entry self_entry = {
- .is_dir = true,
- .full_path = noh_sv_from_string(fs_path),
- .name = (url.count == 0) ? settings.library_name : path_to_name(noh_sv_from_string(fs_path)),
- .modified_at = {0},
- };
- set_modified_time(&self_entry);
- noh_log(NOH_INFO, "Physical path: "Nsv_Fmt, Nsv_Arg(self_entry.full_path));
-
- DIR *dir = opendir(fs_path_cstr.elems);
- if (dir) {
- Library_Entries list = {0};
-
- // Build up list of entries.
- struct dirent *entry = noh_arena_alloc(&temp, sizeof(struct dirent));
- while ((entry = readdir(dir)) != NULL) {
- Library_Entry result = {0};
- if (create_entry(entry, fs_path_cstr.elems, &result)) noh_da_append(&list, result);
- }
-
- // Sort entries, folders first, then by name.
- if (list.count > 1) qsort(list.elems, list.count, sizeof(list.elems[0]), compare_library_entries);
-
- // Create and send response.
- Noh_String response = create_feed_response(self_entry, url, list);
- bool result = send_response(connection, 200, noh_sv_from_string(response), true);
-
- // Cleanup.
- noh_da_free(&list);
- noh_string_free(&response);
- noh_arena_reset(&temp);
- closedir(dir);
-
- return result;
-
- } else if (errno == ENOENT) {
- closedir(dir);
- noh_arena_reset(&temp);
- return send_response(connection, 404, sv(""), false);
-
- } else {
- noh_log(NOH_ERROR, "Opening directory failed.");
- noh_arena_reset(&temp);
- return send_response(connection, 500, sv(""), false);
- }
-}
-
-static enum MHD_Result handle_get(struct MHD_Connection *connection, SV url, SV base_path, SV username) {
- (void)url;
- (void)username;
- (void)base_path;
- return send_response(connection, 200, sv("TODO: Get\n"), false);
-}
+#include "opds_handler.c"
+#include "get_handler.c"
bool is_slash(const char c) { return c == '/'; }
diff --git a/src/opds_handler.c b/src/opds_handler.c
new file mode 100644
index 0000000..1836849
--- /dev/null
+++ b/src/opds_handler.c
@@ -0,0 +1,100 @@
+static SV path_to_name(SV path) {
+ while (true) {
+ SV component = noh_sv_chop_by_delim(&path, '/');
+ if (path.count == 0) return component;
+ }
+}
+
+static void append_entry(Noh_String *response, SV url, Library_Entry entry) {
+ noh_string_append_cstr(response, "");
+ noh_string_append_cstr(response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", Nsv_Arg(get_id(entry.full_path))));
+ noh_string_append_cstr(response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", entry.name));
+ noh_string_append_cstr(response, noh_arena_sprintf(&temp, "%s", format_time(entry)));
+ if (entry.is_dir) {
+ SV opds = sv("opds");
+ Noh_String entry_url = build_path(true, &opds, &url, &entry.name);
+ noh_string_append_cstr(response, noh_arena_sprintf(&temp, "", Nsv_Arg(entry_url)));
+ noh_string_free(&entry_url);
+ } else {
+ SV get = sv("get");
+ Noh_String entry_url = build_path(true, &get, &url, &entry.name);
+ noh_string_append_cstr(response, noh_arena_sprintf(&temp, "", Nsv_Arg(entry_url)));
+ noh_string_free(&entry_url);
+ }
+ noh_string_append_cstr(response, "");
+}
+
+static Noh_String create_feed_response(Library_Entry self_path, SV url, Library_Entries list) {
+ (void)self_path;
+ Noh_String response = {0};
+
+ noh_string_append_cstr(&response, "");
+ noh_string_append_cstr(&response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", Nsv_Arg(get_id(self_path.full_path))));
+ noh_string_append_cstr(&response, noh_arena_sprintf(&temp, ""Nsv_Fmt"", self_path.name));
+ noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "%s", format_time(self_path)));
+
+ SV opds = sv("opds");
+ Noh_String self_url = build_path(true, &opds, &url);
+ noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "", Nsv_Arg(self_url)));
+ noh_string_free(&self_url);
+
+ noh_string_append_cstr(&response, "");
+
+ for (size_t i = 0; i < list.count; i++) append_entry(&response, url, list.elems[i]);
+ noh_string_append_cstr(&response, "");
+
+ return response;
+}
+
+static enum MHD_Result handle_opds(struct MHD_Connection *connection, SV url, Settings settings, SV username) {
+ noh_arena_save(&temp);
+ Noh_String fs_path = build_path(false, &settings.base_path, &username, &url);
+ Noh_String fs_path_cstr = fs_path;
+ noh_string_append_null(&fs_path_cstr);
+
+ Library_Entry self_entry = {
+ .is_dir = true,
+ .full_path = noh_sv_from_string(fs_path),
+ .name = (url.count == 0) ? settings.library_name : path_to_name(noh_sv_from_string(fs_path)),
+ .modified_at = {0},
+ };
+ set_modified_time(&self_entry);
+ noh_log(NOH_INFO, "Physical path: "Nsv_Fmt, Nsv_Arg(self_entry.full_path));
+
+ DIR *dir = opendir(fs_path_cstr.elems);
+ if (dir) {
+ Library_Entries list = {0};
+
+ // Build up list of entries.
+ struct dirent *entry = noh_arena_alloc(&temp, sizeof(struct dirent));
+ while ((entry = readdir(dir)) != NULL) {
+ Library_Entry result = {0};
+ if (create_entry(entry, fs_path_cstr.elems, &result)) noh_da_append(&list, result);
+ }
+
+ // Sort entries, folders first, then by name.
+ if (list.count > 1) qsort(list.elems, list.count, sizeof(list.elems[0]), compare_library_entries);
+
+ // Create and send response.
+ Noh_String response = create_feed_response(self_entry, url, list);
+ bool result = send_response(connection, 200, noh_sv_from_string(response), true);
+
+ // Cleanup.
+ noh_da_free(&list);
+ noh_string_free(&response);
+ noh_arena_reset(&temp);
+ closedir(dir);
+
+ return result;
+
+ } else if (errno == ENOENT) {
+ closedir(dir);
+ noh_arena_reset(&temp);
+ return send_response(connection, 404, sv(""), false);
+
+ } else {
+ noh_log(NOH_ERROR, "Opening directory failed.");
+ noh_arena_reset(&temp);
+ return send_response(connection, 500, sv(""), false);
+ }
+}