Fill in all elements except id

This commit is contained in:
2026-05-15 21:45:45 +02:00
parent 5b7d9f7b2b
commit cd0cb20195
2 changed files with 66 additions and 41 deletions

View File

@@ -24,7 +24,7 @@ elif [ $1 = "run" ]; then
header "Running" header "Running"
make main make main
./output/main 5000 ./library ./output/main 5000 ./library "Testing library"
elif [ $1 = "debug" ]; then elif [ $1 = "debug" ]; then
header "Debugging" header "Debugging"

View File

@@ -50,13 +50,14 @@ int compare_library_entries(const void *a, const void *b) {
return noh_sv_compare(lea.name, leb.name); return noh_sv_compare(lea.name, leb.name);
} }
#define build_path(first, ...) build_path_((first), __VA_ARGS__, NULL) #define build_path(url, first, ...) build_path_((url), (first), __VA_ARGS__, NULL)
Noh_String build_path_(SV *first, ...) { static Noh_String build_path_(bool url, SV *first, ...) {
va_list args; va_list args;
SV *elem_ = first; SV *elem_ = first;
va_start(args, first); va_start(args, first);
bool start = true; bool start = true;
Noh_String result = {0}; Noh_String result = {0};
if (url) noh_string_append_cstr(&result, "/");
while (elem_ != NULL) { while (elem_ != NULL) {
SV elem = *elem_; SV elem = *elem_;
@@ -77,14 +78,14 @@ Noh_String build_path_(SV *first, ...) {
return result; return result;
} }
SV path_to_name(SV path) { static SV path_to_name(SV path) {
while (true) { while (true) {
SV component = noh_sv_chop_by_delim(&path, '/'); SV component = noh_sv_chop_by_delim(&path, '/');
if (path.count == 0) return component; if (path.count == 0) return component;
} }
} }
void set_modified_time(Library_Entry *entry) { static void set_modified_time(Library_Entry *entry) {
struct stat attr; struct stat attr;
if (stat(noh_arena_sprintf(&temp, Nsv_Fmt, entry->full_path), &attr) == 0) { if (stat(noh_arena_sprintf(&temp, Nsv_Fmt, entry->full_path), &attr) == 0) {
struct tm *timeinfo = localtime(&attr.st_mtime); struct tm *timeinfo = localtime(&attr.st_mtime);
@@ -97,10 +98,10 @@ void set_modified_time(Library_Entry *entry) {
/** /**
* Creates a Library entry from a dirent structure. * Creates a Library entry from a dirent structure.
*/ */
bool create_entry(struct dirent *dir_entry, const char *parent_path, Library_Entry *result) { static bool create_entry(struct dirent *dir_entry, const char *parent_path, Library_Entry *result) {
SV name = sv(dir_entry->d_name); SV name = sv(dir_entry->d_name);
SV parent_path_sv = sv(parent_path); SV parent_path_sv = sv(parent_path);
Noh_String full_path = build_path(&parent_path_sv, &name); Noh_String full_path = build_path(false, &parent_path_sv, &name);
int type = dir_entry->d_type; int type = dir_entry->d_type;
// Check if the entry is relevant. // Check if the entry is relevant.
@@ -117,62 +118,78 @@ bool create_entry(struct dirent *dir_entry, const char *parent_path, Library_Ent
return true; return true;
} }
char *format_library_entry(Library_Entry entry, char time_buffer[80]) { /*
strftime(time_buffer, 80, "%Y-%m-%d %H.%M.%S", &entry.modified_at); * 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.
return noh_arena_sprintf( */
&temp, static char *format_time(Library_Entry entry) {
"Entry: %s \npath:'"Nsv_Fmt"'\nName: '"Nsv_Fmt"'\nModified at: %s.\n", static char time_buffer[32];
entry.is_dir ? "Dir" : "File", strftime((char*)&time_buffer, 32, "%Y-%m-%dT%H:%M:%S.0000000Z", &entry.modified_at);
Nsv_Arg(entry.full_path), return (char*)time_buffer;
Nsv_Arg(entry.name),
time_buffer);
} }
static void append_entry(Noh_String *response, Library_Entry entry) { static void append_entry(Noh_String *response, SV url, Library_Entry entry) {
noh_string_append_cstr(response, "<entry>"); noh_string_append_cstr(response, "<entry>");
noh_string_append_cstr(response, "<id>TODO</id>"); noh_string_append_cstr(response, "<id>TODO</id>");
noh_string_append_cstr(response, "<title>TODO</title>"); noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<title>"Nsv_Fmt"</title>", entry.name));
noh_string_append_cstr(response, "<updated>TODO</updated>"); noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<updated>%s</updated>", format_time(entry)));
if (entry.is_dir) { if (entry.is_dir) {
noh_string_append_cstr(response, "<link rel=\"subsection\" href=\"TODO\" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>"); SV opds = sv("opds");
Noh_String entry_url = build_path(true, &opds, &url, &entry.name);
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<link rel=\"subsection\" href=\""Nsv_Fmt"\" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>", Nsv_Arg(entry_url)));
noh_string_free(&entry_url);
} else { } else {
noh_string_append_cstr(response, "<link rel=\"http://opds-spec.org/acquisition\" href=\"TODO\" type=\"application/epub+zip\"/>"); SV get = sv("get");
Noh_String entry_url = build_path(true, &get, &url, &entry.name);
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<link rel=\"http://opds-spec.org/acquisition\" href=\""Nsv_Fmt"\" type=\"application/epub+zip\"/>", Nsv_Arg(entry_url)));
noh_string_free(&entry_url);
} }
noh_string_append_cstr(response, "</entry>"); noh_string_append_cstr(response, "</entry>");
} }
static Noh_String create_feed_response(Library_Entry self_path, Library_Entries list) { static Noh_String create_feed_response(Library_Entry self_path, SV url, Library_Entries list) {
(void)self_path; (void)self_path;
Noh_String response = {0}; Noh_String response = {0};
noh_string_append_cstr(&response, "<feed>"); noh_string_append_cstr(&response, "<feed>");
noh_string_append_cstr(&response, "<id>TODO</id>"); noh_string_append_cstr(&response, "<id>TODO</id>");
noh_string_append_cstr(&response, "<title>TODO</title>"); noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<title>"Nsv_Fmt"</title>", self_path.name));
noh_string_append_cstr(&response, "<updated>TODO</updated>"); noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<updated>%s</updated>", format_time(self_path)));
noh_string_append_cstr(&response, "<link rel=\"self\" href=\"TODO\" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>");
noh_string_append_cstr(&response, "<link rel=\"root\" href=\"TODO\" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>");
for (size_t i = 0; i < list.count; i++) append_entry(&response, list.elems[i]); SV opds = sv("opds");
Noh_String self_url = build_path(true, &opds, &url);
noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<link rel=\"self\" href=\""Nsv_Fmt"\" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>", Nsv_Arg(self_url)));
noh_string_free(&self_url);
noh_string_append_cstr(&response, "<link rel=\"root\" href=\"/opds\" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>");
for (size_t i = 0; i < list.count; i++) append_entry(&response, url, list.elems[i]);
noh_string_append_cstr(&response, "</feed>"); noh_string_append_cstr(&response, "</feed>");
return response; return response;
} }
static enum MHD_Result handle_opds(struct MHD_Connection *connection, SV url, SV base_path, SV username) { 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_arena_save(&temp);
Noh_String fs_path = build_path(&base_path, &username, &url); 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 = { Library_Entry self_entry = {
.is_dir = true, .is_dir = true,
.full_path = noh_sv_from_string(&fs_path), .full_path = noh_sv_from_string(&fs_path),
.name = path_to_name(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}, .modified_at = {0},
}; };
set_modified_time(&self_entry); set_modified_time(&self_entry);
noh_log(NOH_INFO, "Physical path: "Nsv_Fmt, Nsv_Arg(self_entry.full_path)); noh_log(NOH_INFO, "Physical path: "Nsv_Fmt, Nsv_Arg(self_entry.full_path));
DIR *dir = opendir(fs_path.elems); DIR *dir = opendir(fs_path_cstr.elems);
if (dir) { if (dir) {
Library_Entries list = {0}; Library_Entries list = {0};
@@ -180,14 +197,14 @@ static enum MHD_Result handle_opds(struct MHD_Connection *connection, SV url, SV
struct dirent *entry = noh_arena_alloc(&temp, sizeof(struct dirent)); struct dirent *entry = noh_arena_alloc(&temp, sizeof(struct dirent));
while ((entry = readdir(dir)) != NULL) { while ((entry = readdir(dir)) != NULL) {
Library_Entry result = {0}; Library_Entry result = {0};
if (create_entry(entry, fs_path.elems, &result)) noh_da_append(&list, result); if (create_entry(entry, fs_path_cstr.elems, &result)) noh_da_append(&list, result);
} }
// Sort entries, folders first, then by name. // Sort entries, folders first, then by name.
if (list.count > 1) qsort(list.elems, list.count, sizeof(list.elems[0]), compare_library_entries); if (list.count > 1) qsort(list.elems, list.count, sizeof(list.elems[0]), compare_library_entries);
// Create and send response. // Create and send response.
Noh_String response = create_feed_response(self_entry, list); Noh_String response = create_feed_response(self_entry, url, list);
bool result = send_response(connection, 200, noh_sv_from_string(&response), true); bool result = send_response(connection, 200, noh_sv_from_string(&response), true);
// Cleanup. // Cleanup.
@@ -226,7 +243,8 @@ static enum MHD_Result request_handler(
const char *upload_data, size_t *upload_data_size, const char *upload_data, size_t *upload_data_size,
void **con_cls) { void **con_cls) {
(void)con_cls; (void)upload_data; (void)upload_data_size; (void)con_cls; (void)upload_data; (void)upload_data_size;
SV base_path = *(SV*)cls;
Settings settings = *(Settings*)cls;
noh_log(NOH_INFO, "Request: %s %s %s", version, method, _url); noh_log(NOH_INFO, "Request: %s %s %s", version, method, _url);
@@ -252,18 +270,19 @@ static enum MHD_Result request_handler(
noh_sv_trim_left(&url, is_slash); noh_sv_trim_left(&url, is_slash);
SV controller = noh_sv_chop_by_delim(&url, '/'); SV controller = noh_sv_chop_by_delim(&url, '/');
if (noh_sv_eq_ci(controller, sv("opds"))) return handle_opds(connection, url, base_path, username); if (noh_sv_eq_ci(controller, sv("opds"))) return handle_opds(connection, url, settings, username);
if (noh_sv_eq_ci(controller, sv("get"))) return handle_get(connection, url, base_path, username); if (noh_sv_eq_ci(controller, sv("get"))) return handle_get(connection, url, settings.base_path, username);
return send_response(connection, 404, sv(""), false); return send_response(connection, 404, sv(""), false);
} }
void show_usage(char *program_name, char *error) { void show_usage(char *program_name, char *error) {
noh_log(NOH_INFO, "Usage:"); noh_log(NOH_INFO, "Usage:");
noh_log(NOH_INFO, "%s port library_path", program_name); noh_log(NOH_INFO, "%s port library_path library_name", program_name);
noh_log(NOH_INFO, " port: The port to listen to, between 1000 and 65535."); noh_log(NOH_INFO, " port: The port to listen to, between 1000 and 65535.");
noh_log(NOH_INFO, " library_path: The path where the epub libraries are located."); noh_log(NOH_INFO, " library_path: The path where the epub libraries are located.");
noh_log(NOH_INFO, " The root of this path should have a users.txt file with the usernames and passwords of valid users."); noh_log(NOH_INFO, " The root of this path should have a users.txt file with the usernames and passwords of valid users.");
noh_log(NOH_INFO, " For each user, ther must be a subdirectory where this user's library is located."); noh_log(NOH_INFO, " For each user, ther must be a subdirectory where this user's library is located.");
noh_log(NOH_INFO, " library_name: The name of the library shown as the title of each user's root folder.");
noh_log(NOH_INFO, ""); noh_log(NOH_INFO, "");
noh_log(NOH_ERROR, "%s", error); noh_log(NOH_ERROR, "%s", error);
exit(1); exit(1);
@@ -276,13 +295,19 @@ int main(int argc, char **argv)
if (argc < 1) show_usage(program_name, "Port not provided."); if (argc < 1) show_usage(program_name, "Port not provided.");
char *port_str = noh_shift_args(&argc, &argv); char *port_str = noh_shift_args(&argc, &argv);
int port = atoi(port_str); int port = atoi(port_str);
if (port < 1000 || port > 65535) show_usage(program_name, "Invalid port."); if (port < 1000 || port > 65535) show_usage(program_name, "Invalid port.");
if (argc < 1) show_usage(program_name, "Library path not provided."); if (argc < 1) show_usage(program_name, "Library path not provided.");
char *library_path = noh_shift_args(&argc, &argv); char *library_path = noh_shift_args(&argc, &argv);
SV base_path = sv(library_path);
if (argc < 1) show_usage(program_name, "Library name not provided.");
char *library_name = noh_shift_args(&argc, &argv);
Settings settings = {
.base_path = sv(library_path),
.library_name = sv(library_name),
};
temp = noh_arena_init(1 MB); temp = noh_arena_init(1 MB);
struct MHD_Daemon *server; struct MHD_Daemon *server;
@@ -292,7 +317,7 @@ int main(int argc, char **argv)
MHD_USE_INTERNAL_POLLING_THREAD, MHD_USE_INTERNAL_POLLING_THREAD,
port, port,
NULL, NULL, NULL, NULL,
&request_handler, &base_path, &request_handler, &settings,
MHD_OPTION_END); MHD_OPTION_END);
if (!server) { if (!server) {