Split up main.c while keeping a single compilation unit
This commit is contained in:
6
src/get_handler.c
Normal file
6
src/get_handler.c
Normal file
@@ -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);
|
||||||
|
}
|
||||||
35
src/id_store.c
Normal file
35
src/id_store.c
Normal file
@@ -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);
|
||||||
|
}
|
||||||
93
src/library.c
Normal file
93
src/library.c
Normal file
@@ -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;
|
||||||
|
}
|
||||||
244
src/main.c
244
src/main.c
@@ -16,39 +16,13 @@ This will ignore any auth headers and always log in with username demo.
|
|||||||
|
|
||||||
Noh_Arena temp;
|
Noh_Arena temp;
|
||||||
|
|
||||||
typedef struct {
|
#include "id_store.c"
|
||||||
Noh_String path;
|
#include "library.c"
|
||||||
Noh_String id;
|
|
||||||
} Id;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Id *elems;
|
SV base_path;
|
||||||
size_t count;
|
SV library_name;
|
||||||
size_t capacity;
|
} Settings;
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum MHD_Result send_response(struct MHD_Connection *connection, int status, SV message, bool xml) {
|
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(
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
#include "opds_handler.c"
|
||||||
bool is_dir;
|
#include "get_handler.c"
|
||||||
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, "<entry>");
|
|
||||||
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<id>"Nsv_Fmt"</id>", Nsv_Arg(get_id(entry.full_path))));
|
|
||||||
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<title>"Nsv_Fmt"</title>", entry.name));
|
|
||||||
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<updated>%s</updated>", 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, "<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 {
|
|
||||||
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>");
|
|
||||||
}
|
|
||||||
|
|
||||||
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, "<feed>");
|
|
||||||
noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<id>"Nsv_Fmt"</id>", Nsv_Arg(get_id(self_path.full_path))));
|
|
||||||
noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<title>"Nsv_Fmt"</title>", self_path.name));
|
|
||||||
noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<updated>%s</updated>", 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, "<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>");
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_slash(const char c) { return c == '/'; }
|
bool is_slash(const char c) { return c == '/'; }
|
||||||
|
|
||||||
|
|||||||
100
src/opds_handler.c
Normal file
100
src/opds_handler.c
Normal file
@@ -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, "<entry>");
|
||||||
|
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<id>"Nsv_Fmt"</id>", Nsv_Arg(get_id(entry.full_path))));
|
||||||
|
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<title>"Nsv_Fmt"</title>", entry.name));
|
||||||
|
noh_string_append_cstr(response, noh_arena_sprintf(&temp, "<updated>%s</updated>", 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, "<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 {
|
||||||
|
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>");
|
||||||
|
}
|
||||||
|
|
||||||
|
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, "<feed>");
|
||||||
|
noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<id>"Nsv_Fmt"</id>", Nsv_Arg(get_id(self_path.full_path))));
|
||||||
|
noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<title>"Nsv_Fmt"</title>", self_path.name));
|
||||||
|
noh_string_append_cstr(&response, noh_arena_sprintf(&temp, "<updated>%s</updated>", 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, "<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>");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user