diff --git a/build.sh b/build.sh index 89e3509..631a22b 100755 --- a/build.sh +++ b/build.sh @@ -24,7 +24,7 @@ elif [ $1 = "run" ]; then header "Running" make main - ./output/main 5000 ./library + ./output/main 5000 ./library "Testing library" elif [ $1 = "debug" ]; then header "Debugging" diff --git a/src/main.c b/src/main.c index 6fd1b78..841c709 100644 --- a/src/main.c +++ b/src/main.c @@ -50,13 +50,14 @@ int compare_library_entries(const void *a, const void *b) { return noh_sv_compare(lea.name, leb.name); } -#define build_path(first, ...) build_path_((first), __VA_ARGS__, NULL) -Noh_String build_path_(SV *first, ...) { +#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_; @@ -77,14 +78,14 @@ Noh_String build_path_(SV *first, ...) { return result; } -SV path_to_name(SV path) { +static SV path_to_name(SV path) { while (true) { SV component = noh_sv_chop_by_delim(&path, '/'); if (path.count == 0) return component; } } -void set_modified_time(Library_Entry *entry) { +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); @@ -97,10 +98,10 @@ void set_modified_time(Library_Entry *entry) { /** * 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 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; // 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; } -char *format_library_entry(Library_Entry entry, char time_buffer[80]) { - strftime(time_buffer, 80, "%Y-%m-%d %H.%M.%S", &entry.modified_at); - - return noh_arena_sprintf( - &temp, - "Entry: %s \npath:'"Nsv_Fmt"'\nName: '"Nsv_Fmt"'\nModified at: %s.\n", - entry.is_dir ? "Dir" : "File", - Nsv_Arg(entry.full_path), - Nsv_Arg(entry.name), - time_buffer); +/* + * 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, Library_Entry entry) { +static void append_entry(Noh_String *response, SV url, Library_Entry entry) { noh_string_append_cstr(response, ""); noh_string_append_cstr(response, "TODO"); - noh_string_append_cstr(response, "TODO"); - noh_string_append_cstr(response, "TODO"); + 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) { - noh_string_append_cstr(response, ""); + 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 { - noh_string_append_cstr(response, ""); + 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, Library_Entries list) { +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, "TODO"); - noh_string_append_cstr(&response, "TODO"); - noh_string_append_cstr(&response, "TODO"); - noh_string_append_cstr(&response, ""); - noh_string_append_cstr(&response, ""); + 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))); - 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, "", 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, 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_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 = { .is_dir = true, .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}, }; set_modified_time(&self_entry); 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) { 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)); while ((entry = readdir(dir)) != NULL) { 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. 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, list); + Noh_String response = create_feed_response(self_entry, url, list); bool result = send_response(connection, 200, noh_sv_from_string(&response), true); // Cleanup. @@ -226,7 +243,8 @@ static enum MHD_Result request_handler( const char *upload_data, size_t *upload_data_size, void **con_cls) { (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); @@ -252,18 +270,19 @@ static enum MHD_Result request_handler( noh_sv_trim_left(&url, is_slash); 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("get"))) return handle_get(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, settings.base_path, username); return send_response(connection, 404, sv(""), false); } void show_usage(char *program_name, char *error) { 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, " 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, " 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_ERROR, "%s", error); exit(1); @@ -276,13 +295,19 @@ int main(int argc, char **argv) if (argc < 1) show_usage(program_name, "Port not provided."); char *port_str = noh_shift_args(&argc, &argv); - int port = atoi(port_str); if (port < 1000 || port > 65535) show_usage(program_name, "Invalid port."); if (argc < 1) show_usage(program_name, "Library path not provided."); 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); struct MHD_Daemon *server; @@ -292,7 +317,7 @@ int main(int argc, char **argv) MHD_USE_INTERNAL_POLLING_THREAD, port, NULL, NULL, - &request_handler, &base_path, + &request_handler, &settings, MHD_OPTION_END); if (!server) {