ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_app_prepare.c
Go to the documentation of this file.
1
9#include "mdl_app_internal.h"
10
33 const char* url,
34 const mdl_net_req_t* request,
35 char* buffer,
36 size_t capacity,
37 size_t* out_length,
38 mdl_net_resp_t* response)
39{
40 return mdl_net_get_buf((mdl_net_iface_t*)context,
41 url,
42 request,
43 buffer,
44 capacity,
45 out_length,
46 response);
47}
48
62{
64 ra8_err_t error = k_ra8_ok;
65 if (result->index_rebuilt) {
66 error = priv_mdl_stream_text(error, stream, "mdl: discarded corrupt cache index for ");
67 error = priv_mdl_stream_text(error, stream, url);
68 error = priv_mdl_stream_text(error, stream, "\n");
69 }
70 if ((error == k_ra8_ok) && result->body_reused) {
71 error = priv_mdl_stream_text(error, stream, "mdl: cache reuse age=");
72 if (result->age_seconds >= 0) {
73 error = priv_mdl_stream_u64(error, stream, (uint64_t)result->age_seconds);
74 error = priv_mdl_stream_text(error, stream, "s");
75 } else {
76 error = priv_mdl_stream_text(error, stream, "unknown");
77 }
78 error = priv_mdl_stream_text(error, stream, " status=");
79 error = priv_mdl_stream_u64(error, stream, result->observed_status);
80 error = priv_mdl_stream_text(error, stream, " url=");
81 error = priv_mdl_stream_text(error, stream, url);
82 error = priv_mdl_stream_text(error, stream, "\n");
83 }
84 return error;
85}
86
89
107internal_prepare_diag3(const char* first, const char* second, const char* third)
108{
110 ra8_err_t error = priv_mdl_stream_text(k_ra8_ok, diagnostic, first);
111 error = priv_mdl_stream_text(error, diagnostic, second);
112 return priv_mdl_stream_text(error, diagnostic, third);
113}
114
128RA8_INTERNAL static void internal_swap_rows(mdl_url_list_t* l, size_t a, size_t b)
129{
130 if (a == b) {
131 return;
132 }
134 memcpy(l->urls[a], l->urls[b], k_mdl_url_max);
136}
137
151{
152 for (size_t i = 0U; i < (l->count / 2U); ++i) {
153 internal_swap_rows(l, i, l->count - 1U - i);
154 }
155}
156
173{
174 for (size_t i = 0U; i < l->count; ++i) {
175 double ignored = 0.0;
176 if (!mdl_urlname_chapter_parse(l->urls[i], &ignored)) {
177 return false;
178 }
179 }
180 for (size_t i = 0U; i + 1U < l->count; ++i) {
181 size_t min = i;
182 for (size_t j = i + 1U; j < l->count; ++j) {
184 min = j;
185 }
186 }
187 internal_swap_rows(l, i, min);
188 }
189 return true;
190}
191
209{
210 if (order == k_mdl_order_reverse) {
212 } else if (order == k_mdl_order_asc) {
215 priv_mdl_app_context()->diagnostic,
216 "mdl: WARNING: at least one chapter has no numeric "
217 "identifier; preserving site document order\n");
218 }
219 }
220 return k_ra8_ok;
221}
222
235RA8_INTERNAL static void internal_filter_prefix(mdl_url_list_t* l, const char* prefix)
236{
237 const size_t plen = strlen(prefix);
238 size_t w = 0U;
239 for (size_t r = 0U; r < l->count; ++r) {
240 if (strncmp(l->urls[r], prefix, plen) == 0) {
241 if (w != r) {
242 memcpy(l->urls[w], l->urls[r], k_mdl_url_max);
243 }
244 ++w;
245 }
246 }
247 l->count = w;
248}
249
265{
266 static bool s_warned = false;
267 if (!s_warned) {
268 const ra8_err_t error =
270 priv_mdl_app_context()->diagnostic,
271 "mdl: WARNING: no contact configured; pass --contact "
272 "<email|url> so a site operator can reach you before banning\n");
273 if (error == k_ra8_ok) {
274 s_warned = true;
275 }
276 return error;
277 }
278 return k_ra8_ok;
279}
280
300internal_extract_optional_metadata(const mdl_site_t* site, const char* html, size_t len)
301{
302 const struct {
303 const char* selector;
304 char* out;
305 size_t cap;
306 const char* label;
307 } optional[] = {{site->series_summary_selector,
310 "summary"},
314 "author"},
318 "artist"}};
319 for (size_t i = 0U; i < (sizeof(optional) / sizeof(optional[0])); ++i) {
320 if (optional[i].selector[0] == '\0') {
321 continue;
322 }
323 const ra8_err_t rc =
324 mdl_extract_selector(html, len, optional[i].selector, optional[i].out, optional[i].cap);
325 if (rc == k_ra8_err_invalid_size) {
326 const ra8_err_t output_error =
327 internal_prepare_diag3("mdl: series ",
328 optional[i].label,
329 " exceeds the bounded metadata field\n");
330 return (output_error == k_ra8_ok) ? rc : output_error;
331 }
332 if (rc != k_ra8_ok) {
333 optional[i].out[0] = '\0';
334 ra8_err_t output_error =
335 internal_prepare_diag3("mdl: WARNING: series ", optional[i].label, " selector '");
336 output_error = priv_mdl_stream_text(output_error,
337 priv_mdl_app_context()->diagnostic,
338 optional[i].selector);
339 output_error = priv_mdl_stream_text(output_error,
340 priv_mdl_app_context()->diagnostic,
341 "' matched no bounded value\n");
342 if (output_error != k_ra8_ok) {
343 return output_error;
344 }
345 }
346 }
347 return k_ra8_ok;
348}
349
371internal_extract_cover(const mdl_site_t* site, const char* series_url, const char* html, size_t len)
372{
373 if (site->series_cover_selector[0] == '\0') {
374 return k_ra8_ok;
375 }
376 char cover_raw[k_mdl_url_max];
377 const ra8_err_t rc =
378 mdl_extract_selector(html, len, site->series_cover_selector, cover_raw, sizeof(cover_raw));
379 if (rc != k_ra8_ok) {
380 const ra8_err_t output_error = internal_prepare_diag3("mdl: required series cover selector '",
382 "' failed\n");
383 return (output_error == k_ra8_ok) ? rc : output_error;
384 }
385 if (!mdl_extract_resolve_url(series_url,
386 cover_raw,
387 priv_mdl_app_context()->series_metadata.cover_url,
388 sizeof(priv_mdl_app_context()->series_metadata.cover_url))) {
389 const ra8_err_t output_error =
391 priv_mdl_app_context()->diagnostic,
392 "mdl: series cover URL could not be resolved safely\n");
393 return (output_error == k_ra8_ok) ? k_ra8_err_validation_failed : output_error;
394 }
395 return k_ra8_ok;
396}
397
427 const char* series_url,
428 const char* html,
429 size_t len)
430{
431 memset(&priv_mdl_app_context()->series_metadata,
432 0,
433 sizeof(priv_mdl_app_context()->series_metadata));
436 const int language_len = snprintf(priv_mdl_app_context()->series_metadata.language,
437 sizeof(priv_mdl_app_context()->series_metadata.language),
438 "%s",
439 site->language);
440 if ((language_len < 0) ||
441 ((size_t)language_len >= sizeof(priv_mdl_app_context()->series_metadata.language))) {
443 }
444
446 if (site->series_title_selector[0] != '\0') {
447 rc = mdl_extract_selector(html,
448 len,
450 priv_mdl_app_context()->series_metadata.title,
451 sizeof(priv_mdl_app_context()->series_metadata.title));
453 }
454 if (rc == k_ra8_err_invalid_size) {
455 const ra8_err_t output_error =
457 priv_mdl_app_context()->diagnostic,
458 "mdl: series title exceeds the bounded metadata field\n");
459 return (output_error == k_ra8_ok) ? rc : output_error;
460 }
461 if (rc != k_ra8_ok) {
462 mdl_urlname_last_segment(series_url,
463 priv_mdl_app_context()->series_metadata.title,
464 sizeof(priv_mdl_app_context()->series_metadata.title));
465 ra8_err_t output_error = internal_prepare_diag3("mdl: WARNING: series title selector '",
467 "' failed; using URL leaf '");
468 output_error = priv_mdl_stream_text(output_error,
469 priv_mdl_app_context()->diagnostic,
470 priv_mdl_app_context()->series_metadata.title);
471 output_error = priv_mdl_stream_text(output_error, priv_mdl_app_context()->diagnostic, "'\n");
472 if (output_error != k_ra8_ok) {
473 return output_error;
474 }
475 }
476
477 rc = internal_extract_optional_metadata(site, html, len);
478 if (rc != k_ra8_ok) {
479 return rc;
480 }
481 return internal_extract_cover(site, series_url, html, len);
482}
483
501 const char* series_url)
502{
503 char prefix[k_dir_path_bytes];
504 if (site->chapter_url_prefix[0] != '\0') {
505 const int prefix_len = snprintf(prefix, sizeof(prefix), "%s", site->chapter_url_prefix);
506 if ((prefix_len < 0) || ((size_t)prefix_len >= sizeof(prefix))) {
508 }
509 } else {
510 const int prefix_len = snprintf(prefix, sizeof(prefix), "%s", series_url);
511 if ((prefix_len < 0) || ((size_t)prefix_len >= sizeof(prefix))) {
513 }
514 size_t pl = strlen(prefix);
515 if ((pl > 0U) && (prefix[pl - 1U] != '/') && (pl + 1U < sizeof(prefix))) {
516 prefix[pl] = '/';
517 prefix[pl + 1U] = '\0';
518 }
519 }
520 internal_filter_prefix(&priv_mdl_app_context()->chapters, prefix);
521 return internal_apply_order(&priv_mdl_app_context()->chapters, site->chapter_order);
522}
523
548 const char* series_url,
549 uint32_t timeout,
550 mdl_cache_t* cache)
551{
552 if ((cache == nullptr) ||
553 !mdl_session_url_allowed(&priv_mdl_app_context()->session, series_url, nullptr)) {
554 return k_ra8_fail; /* robots refused the series page (message printed) */
555 }
556 const mdl_net_req_t req = {.user_agent = priv_mdl_app_context()->session.user_agent,
557 .referer = nullptr,
558 .timeout_ms = timeout};
559 size_t len = 0U;
560 mdl_net_resp_t response = {};
561 mdl_cache_result_t cache_result;
562 ra8_err_t rc = mdl_cache_get_buf(cache,
563 series_url,
564 &req,
566 priv_mdl_app_context()->session.net,
567 priv_mdl_app_context()->page,
568 sizeof(priv_mdl_app_context()->page),
569 &len,
570 &response,
571 &cache_result);
572 if (rc != k_ra8_ok) {
573 return rc;
574 }
575 rc = priv_mdl_app_report_cache(series_url, &cache_result);
576 if (rc != k_ra8_ok) {
577 return rc;
578 }
579 rc = internal_extract_series_metadata(site, series_url, priv_mdl_app_context()->page, len);
580 if (rc != k_ra8_ok) {
581 return rc;
582 }
584 len,
585 series_url,
587 &priv_mdl_app_context()->chapters);
588 if (rc != k_ra8_ok) {
589 return rc;
590 }
591
592 return internal_prepare_filter_chapters(site, series_url);
593}
594
613 const mdl_run_opts_t* opts,
614 const char* cfg_contact,
615 char* ua,
616 size_t ua_cap)
617{
618 const char* contact = opts->contact;
619 if ((contact == nullptr) && (cfg_contact != nullptr) && (cfg_contact[0] != '\0')) {
620 contact = cfg_contact; /* CLI --contact overrides the site descriptor's key */
621 }
622 if (!mdl_session_build_ua(contact, ua, ua_cap)) {
623 const ra8_err_t error = internal_warn_no_contact();
624 if (error != k_ra8_ok) {
625 return error;
626 }
627 }
629 net,
630 ua,
631 priv_mdl_app_context()->diagnostic,
632 opts->honor_robots);
633 return k_ra8_ok;
634}
635
658 const char* series_url,
659 char* slug,
660 size_t slug_cap,
661 char* abs_dir)
662{
663 mdl_urlname_last_segment(series_url, slug, slug_cap);
665 if (priv_mdl_app_storage_ensure_directory(storage, out_dir) != k_ra8_ok) {
666 (void)internal_prepare_diag3("mdl: library root is not a directory: ", out_dir, "\n");
667 return false;
668 }
669 if (!mdl_join_dir_under(storage, out_dir, slug, abs_dir, (size_t)k_fw_fs_path_cap)) {
670 (void)internal_prepare_diag3("mdl: refusing unsafe series path for slug '", slug, "'\n");
671 return false;
672 }
673 return true;
674}
@ k_fw_fs_path_cap
Largest portable path including its NUL.
mdl_app_context_t * priv_mdl_app_context(void)
The working set the composition root bound with mdl_app_bind.
Definition mdl_app.c:33
Module-private contract shared by the application-mode translation units.
@ k_dir_path_bytes
Directory-path buffer.
bool priv_mdl_app_prepare_series_dir(const char *out_dir, const char *series_url, char *slug, size_t slug_cap, char *abs_dir)
Create and resolve a series directory beneath an output root.
ra8_err_t priv_mdl_app_prepare_chapters(const mdl_site_t *site, const char *series_url, uint32_t timeout, mdl_cache_t *cache)
Fetch a series page and build its ordered chapter list.
static ra8_err_t internal_extract_series_metadata(const mdl_site_t *site, const char *series_url, const char *html, size_t len)
Extract descriptor-driven series metadata from one fetched page.
ra8_err_t priv_mdl_app_start_session(mdl_net_iface_t *net, const mdl_run_opts_t *opts, const char *cfg_contact, char *ua, size_t ua_cap)
Initialize the shared network session identity for one run.
ra8_err_t priv_mdl_app_report_cache(const char *url, const mdl_cache_result_t *result)
Report verified cache reuse with its pre-request staleness.
static ra8_err_t internal_extract_cover(const mdl_site_t *site, const char *series_url, const char *html, size_t len)
Extract and resolve the configured required cover URL.
static ra8_err_t internal_prepare_filter_chapters(const mdl_site_t *site, const char *series_url)
Retain only chapters belonging to the configured series.
static ra8_err_t internal_prepare_diag3(const char *first, const char *second, const char *third)
Append three borrowed preparation diagnostics.
static ra8_err_t internal_prepare_cache_fetch(void *context, const char *url, const mdl_net_req_t *request, char *buffer, size_t capacity, size_t *out_length, mdl_net_resp_t *response)
Dispatch one cache request directly through the active network seam.
static void internal_filter_prefix(mdl_url_list_t *l, const char *prefix)
Keep only list entries whose URL starts with a prefix.
static ra8_err_t internal_warn_no_contact(void)
Print the missing-operator-contact warning once.
static ra8_err_t internal_extract_optional_metadata(const mdl_site_t *site, const char *html, size_t len)
Extract the optional summary and creator fields.
static bool internal_sort_by_chapter_num(mdl_url_list_t *l)
Sort a URL list by parsed chapter number.
static ra8_err_t internal_apply_order(mdl_url_list_t *l, mdl_chapter_order_t order)
Apply the configured chapter ordering in place.
static void internal_swap_rows(mdl_url_list_t *l, size_t a, size_t b)
Swap two rows of a URL list.
static void internal_reverse_list(mdl_url_list_t *l)
Reverse a URL list in place.
static char s_rowtmp[k_mdl_url_max]
Scratch row used while reordering the prepared chapter list.
ra8_err_t priv_mdl_app_storage_ensure_directory(mdl_storage_t *storage, const char *path)
Ensure one canonical portable path names a real directory.
ra8_err_t mdl_cache_get_buf(mdl_cache_t *cache, const char *url, const mdl_net_req_t *base_request, mdl_cache_fetch_fn fetch, void *fetch_context, char *buffer, size_t capacity, size_t *out_length, mdl_net_resp_t *response, mdl_cache_result_t *result)
Fetch one document through the per-host persistent cache.
Definition mdl_cache.c:537
mdl_chapter_order_t
How to order chapters extracted from a series page.
Definition mdl_config.h:25
@ k_mdl_order_asc
Sort by the number parsed from the URL.
Definition mdl_config.h:28
@ k_mdl_order_reverse
Reverse document order (newest-first -> old).
Definition mdl_config.h:27
ra8_err_t mdl_extract_anchors(const char *html, size_t html_len, const char *base_url, const char *href_contains, mdl_url_list_t *out)
Scan html for <a href> links, resolved to absolute form.
@ k_mdl_url_max
Max bytes per URL, including the NUL.
Definition mdl_extract.h:25
bool mdl_extract_resolve_url(const char *base, const char *raw, char *out, size_t out_cap)
Resolve a possibly relative HTTP(S) URL against an absolute base URL.
ra8_err_t mdl_extract_selector(const char *html, size_t html_len, const char *selector, char *out, size_t out_cap)
Extract one bounded metadata value using a site-descriptor selector.
ra8_err_t mdl_net_get_buf(mdl_net_iface_t *net, const char *url, const mdl_net_req_t *req, char *buf, size_t cap, size_t *out_len, mdl_net_resp_t *resp)
GET url fully into a caller buffer (used for HTML pages).
Definition mdl_net.c:78
bool mdl_join_dir_under(mdl_storage_t *storage, const char *parent_abs, const char *seg, char *out, size_t cap)
Join seg under parent_abs, create it, and verify it stays inside.
Definition mdl_pathfs.c:15
bool mdl_session_url_allowed(mdl_session_t *session, const char *url, uint32_t *crawl_delay_ms)
Decide whether url may be fetched under robots.txt, and its delay.
void mdl_session_init(mdl_session_t *session, mdl_net_iface_t *net, const char *user_agent, ra8_io_stream_t *diagnostic, bool honor_robots)
Initialise a session over a network backend.
Definition mdl_session.c:61
bool mdl_session_build_ua(const char *contact, char *out, size_t cap)
Build the truthful default User-Agent into out.
Definition mdl_session.c:47
@ k_mdl_state_read_ltr
Left-to-right page progression.
Definition mdl_state.h:109
@ k_mdl_state_read_rtl
Right-to-left page progression.
Definition mdl_state.h:110
ra8_err_t priv_mdl_stream_text(ra8_err_t prior, ra8_io_stream_t *stream, const char *text)
Append text unless an earlier operation already failed.
Definition mdl_stream.c:16
ra8_err_t priv_mdl_stream_u64(ra8_err_t prior, ra8_io_stream_t *stream, uint64_t value)
Append one unsigned integer unless an earlier append failed.
Definition mdl_stream.c:21
double mdl_urlname_chapter_value(const char *url)
Parse a chapter URL as a possibly-decimal chapter value.
void mdl_urlname_last_segment(const char *url, char *out, size_t cap)
Sanitised last non-empty path segment of a URL.
Definition mdl_urlname.c:75
bool mdl_urlname_chapter_parse(const char *url, double *out)
Parse an explicitly-marked integral or decimal chapter value.
#define RA8_PRIV
Module-private helper: shared across TUs but only inside one library.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_no_data
No application data available (e.g.
Definition ra8_err.h:202
@ k_ra8_fail
Generic unspecified failure.
Definition ra8_err.h:133
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
int strncmp(const char *s1, const char *s2, size_t n)
Compare two strings up to a specified length.
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
size_t strlen(const char *s)
Calculate string length.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
mdl_series_metadata_t series_metadata
Current scraped metadata.
Definition mdl_app.h:179
mdl_session_t session
Network policy session.
Definition mdl_app.h:163
mdl_storage_t storage
Injected filesystem facade.
Definition mdl_app.h:165
ra8_io_stream_t * diagnostic
Borrowed diagnostic sink.
Definition mdl_app.h:167
Observable outcome of one cache lookup.
Definition mdl_cache.h:105
bool body_reused
Returned bytes came from verified storage.
Definition mdl_cache.h:108
bool index_rebuilt
A corrupt index was discarded first.
Definition mdl_cache.h:110
int64_t age_seconds
Age before this operation, or -1 if unknown.
Definition mdl_cache.h:106
uint16_t observed_status
Network or retained status.
Definition mdl_cache.h:107
One non-reentrant cache binding over caller storage.
Definition mdl_cache.h:92
A network backend handle: an immutable method table plus its state.
Per-request session parameters.
Definition mdl_net.h:43
Per-transfer response metadata surfaced to the politeness governor.
Definition mdl_net.h:120
Cross-cutting policy and injected transport threaded into every mode.
Definition mdl_app.h:73
const char * contact
Operator contact override, or NULL.
Definition mdl_app.h:76
bool honor_robots
False when robots.txt is ignored.
Definition mdl_app.h:77
char artist[k_mdl_person_max]
Artist/illustrator.
Definition mdl_app.h:97
mdl_state_reading_direction_t direction
Page progression.
Definition mdl_app.h:100
bool title_selected
Title selector succeeded.
Definition mdl_app.h:101
char summary[k_mdl_summary_max]
Synopsis/description.
Definition mdl_app.h:95
char writer[k_mdl_person_max]
Writer/author.
Definition mdl_app.h:96
const char * user_agent
Full UA header (caller-owned).
Definition mdl_session.h:41
One site's scraping rules (all data, no logic).
Definition mdl_config.h:46
char series_author_selector[k_mdl_match_max]
Series writer selector.
Definition mdl_config.h:75
char reading_direction[k_mdl_kind_max]
ltr or rtl.
Definition mdl_config.h:83
char series_cover_selector[k_mdl_match_max]
Series cover-URL selector.
Definition mdl_config.h:77
char chapter_url_contains[k_mdl_match_max]
href must contain this.
Definition mdl_config.h:53
char language[k_mdl_kind_max]
BCP-47 language tag.
Definition mdl_config.h:82
char series_summary_selector[k_mdl_match_max]
Series synopsis selector.
Definition mdl_config.h:73
mdl_chapter_order_t chapter_order
Ordering to apply.
Definition mdl_config.h:56
char series_artist_selector[k_mdl_match_max]
Series artist selector.
Definition mdl_config.h:76
char chapter_url_prefix[k_mdl_search_url_max]
Absolute series-chapter prefix.
Definition mdl_config.h:54
char series_title_selector[k_mdl_match_max]
Series display-title selector.
Definition mdl_config.h:71
One non-reentrant downloader filesystem dependency bundle.
Definition mdl_storage.h:40
Bounded list of absolute URLs found on a page.
Definition mdl_extract.h:29
char urls[k_mdl_max_urls][k_mdl_url_max]
Absolute, NUL-terminated.
Definition mdl_extract.h:30
size_t count
Number of valid entries.
Definition mdl_extract.h:31
Caller-allocated byte-stream handle binding a sink to its context.
#define min(x, y)
Untyped minimum shim used by the SOUP's buffer clamping.
Definition xz_config.h:157