21typedef enum : uint16_t {
44 uint16_t response_status)
46 const bool legacy_unknown = (fetched_at == 0) && (response_status == 0U);
49 return legacy_unknown || ((fetched_at >= 0) && http_status);
57 memset(st, 0,
sizeof(*st));
65 (void)snprintf(dst, cap,
"%s", val);
72 if (text ==
nullptr) {
75 const size_t len =
strnlen(text, cap);
76 return (len < cap) && (strpbrk(text,
"\t\r\n") ==
nullptr);
100 if (path[0] ==
'\0') {
103 if ((path[0] ==
'/') || (path[0] ==
'\\')) {
106 const char* part = path;
107 while (*part !=
'\0') {
108 const char* end = strpbrk(part,
"/\\");
109 const size_t len = (end !=
nullptr) ? (
size_t)(end - part) :
strlen(part);
110 if ((len == 0U) || ((len == 2U) && (part[0] ==
'.') && (part[1] ==
'.'))) {
113 if (end ==
nullptr) {
138 return isfinite(number) && (known || ((number == 0.0) && !signbit(number)));
190 const char* cover_url,
191 const char* cover_path,
192 const char* language,
210 (void)snprintf(next_summary,
sizeof(next_summary),
"%s", summary);
211 (void)snprintf(next_writer,
sizeof(next_writer),
"%s", writer);
212 (void)snprintf(next_artist,
sizeof(next_artist),
"%s", artist);
213 (void)snprintf(next_cover_url,
sizeof(next_cover_url),
"%s", cover_url);
214 (void)snprintf(next_cover_path,
sizeof(next_cover_path),
"%s", cover_path);
215 (void)snprintf(next_language,
sizeof(next_language),
"%s", language);
229 const char* site_name,
230 const char* site_host,
231 const char* config_path)
245 if ((st ==
nullptr) || (
id ==
nullptr)) {
268 if ((st ==
nullptr) || (
id ==
nullptr) || (url ==
nullptr)) {
277 if (existing !=
nullptr) {
284 memset(rec, 0,
sizeof(*rec));
302 (void)snprintf(chapter->
title,
sizeof(chapter->
title),
"%s", title);
310 if ((st ==
nullptr) || (
id ==
nullptr)) {
323 if ((st ==
nullptr) || (
id ==
nullptr)) {
341 return &st->
pages[i];
349 uint64_t content_hash,
350 const char* rel_path,
352 const char* last_modified,
354 uint16_t response_status)
356 if ((st ==
nullptr) || (rel_path ==
nullptr)) {
373 if (rec ==
nullptr) {
383 (void)snprintf(rec->
etag,
sizeof(rec->
etag),
"%s", (etag !=
nullptr) ? etag :
"");
387 (last_modified !=
nullptr) ? last_modified :
"");
396 uint16_t response_status)
399 (response_status == 0U)) {
433 if (!chapter->
number_known || ((
long double)chapter->
number < (
long double)LONG_MIN) ||
434 ((
long double)chapter->
number > (
long double)LONG_MAX)) {
437 const long number = (long)chapter->
number;
438 if ((
double)number != chapter->
number) {
483 *lo = (num < *lo) ? num : *lo;
484 *hi = (num > *hi) ? num : *hi;
509 long chapter_number = 0L;
512 (chapter_number == num)) {
541 for (
long num = lo; (num <= hi) && (listed < (size_t)
k_state_gap_cap); ++num) {
545 const int w = snprintf(&buf[len],
546 (len < cap) ? (cap - len) : 0U,
548 wrote ?
", " :
", missing ",
550 if ((w < 0) || ((
size_t)w >= ((len < cap) ? (cap - len) : 0U))) {
561 if ((st ==
nullptr) || (buf ==
nullptr) || (cap == 0U)) {
568 size_t complete = 0U;
572 if (complete == 0U) {
573 (void)snprintf(buf, cap,
"no chapters complete");
575 (void)snprintf(buf, cap,
"%zu chapter(s) complete", complete);
579 (void)snprintf(buf, cap,
"%zu chapter(s) complete, %ld..%ld", n, lo, hi);
@ k_mdl_last_mod_max
Raw Last-Modified header value buffer bytes.
@ k_mdl_etag_max
Raw ETag header value buffer bytes.
uint16_t mdl_state_chapter_pages(const mdl_state_t *st, const char *id)
Recorded page count for a chapter (0 when unknown).
void priv_mdl_state_set_opt(char *dst, size_t cap, const char *val)
Copy val into a bounded field when val is non-NULL.
static void internal_mdl_state_append_gaps(const mdl_state_t *st, long lo, long hi, char *buf, size_t cap)
Append the missing-chapter list within [lo,hi] to buf (bounded).
void mdl_state_coverage(const mdl_state_t *st, char *buf, size_t cap)
Render a one-line coverage summary (chapter span, count, gaps).
bool mdl_state_set_series_metadata(mdl_state_t *st, const char *summary, const char *writer, const char *artist, const char *cover_url, const char *cover_path, const char *language, mdl_state_reading_direction_t direction)
Set the optional rich metadata persisted for a series.
mdl_state_coverage_t
Maximum missing chapter numbers rendered in one coverage summary.
@ k_state_gap_cap
Max missing chapters listed.
@ k_state_http_status_min
Smallest valid HTTP status.
@ k_state_http_status_max
Largest valid HTTP status.
bool priv_mdl_state_valid(const mdl_state_t *st)
Validate every bound and cross-field invariant before persistence.
static bool internal_mdl_state_page_response_valid(int64_t fetched_at, uint16_t response_status)
Whether persisted HTTP observation metadata is canonical.
static bool internal_mdl_state_complete_span(const mdl_state_t *st, long *lo, long *hi, size_t *n)
Number span [lo,hi] of complete integral chapters.
static bool internal_mdl_state_has_complete_number(const mdl_state_t *st, long num)
True when a complete chapter with parsed number num exists.
mdl_chapter_rec_t * mdl_state_add_chapter(mdl_state_t *st, const char *id, const char *url, long number)
Find or append a chapter record, returning it.
bool mdl_state_set_chapter_metadata(mdl_chapter_rec_t *chapter, const char *title, double number, bool number_known)
Set a chapter's display title and explicit parsed number.
mdl_chapter_rec_t * mdl_state_find_chapter(mdl_state_t *st, const char *id)
Find a chapter record by its stable identifier.
const mdl_page_rec_t * mdl_state_find_page(const mdl_state_t *st, uint64_t url_hash)
Find a page record by its source-URL hash (the dedup lookup).
bool mdl_state_note_page_response(mdl_state_t *st, uint64_t url_hash, int64_t fetched_at, uint16_t response_status)
Refresh the observed HTTP result for one existing URL cache entry.
mdl_chapter_rec_t * mdl_state_add_chapter_numbered(mdl_state_t *st, const char *id, const char *url, double number, bool number_known)
Find or append a chapter with explicit parsed-number presence.
bool priv_mdl_state_relative_path_valid(const char *path, size_t cap)
Validate a bounded relative path.
static bool internal_mdl_state_chapter_number_valid(double number, bool known)
Validate an explicit chapter-number tuple.
bool mdl_state_chapter_complete(const mdl_state_t *st, const char *id)
Whether a chapter is recorded fully fetched and verified.
bool priv_mdl_state_field_valid(const char *text, size_t cap)
True when a record field fits and cannot inject a line or column.
bool mdl_state_add_page(mdl_state_t *st, uint64_t url_hash, uint64_t content_hash, const char *rel_path, const char *etag, const char *last_modified, int64_t fetched_at, uint16_t response_status)
Add or replace a URL-keyed page cache record.
void mdl_state_init(mdl_state_t *st)
Reset a state object to an empty, current-version library.
void mdl_state_set_series(mdl_state_t *st, const char *url, const char *title, const char *site_name, const char *site_host, const char *config_path)
Record the series identity and the descriptor used.
static bool internal_mdl_state_chapter_number_as_long(const mdl_chapter_rec_t *chapter, long *out)
Convert an exactly integral chapter number to long.
Persistent per-series library state for the media downloader.
mdl_state_reading_direction_t
Persisted fixed-layout reading direction.
@ k_mdl_state_read_ltr
Left-to-right page progression.
@ k_mdl_state_read_rtl
Right-to-left page progression.
@ k_mdl_language_max
BCP-47 language bytes.
@ k_mdl_state_version
Timestamped cache schema written now.
@ k_mdl_person_max
Writer/artist bytes.
@ k_mdl_summary_max
Series summary bytes.
@ k_mdl_chapter_id_max
Chapter identifier bytes (sanitised).
@ k_mdl_max_chapters
Chapters tracked per series.
@ k_mdl_relpath_max
Page path relative to the series dir.
@ k_mdl_max_page_recs
Per-page content records per series.
Module-private validation shared by the state model and codec.
Annotation-attribute framework macros for ra8-firmware.
#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).
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.
size_t strnlen(const char *s, size_t maxlen)
Calculate bounded string length.
One chapter's coverage in library state.
char chapter_id[k_mdl_chapter_id_max]
Stable identifier (URL leaf).
double number
Parsed number; 0 may be valid.
char source_url[k_mdl_url_max]
Chapter page URL.
uint16_t pages_done
Pages fetched and verified.
bool number_known
Whether number is known.
char title[k_mdl_title_max]
Display title, or empty.
uint16_t page_count
Total pages known (0 = ?).
bool complete
All pages present + verified.
One page's dedup/verify record in the series-wide pool.
char etag[k_mdl_etag_max]
Cached ETag for conditional GET.
char last_modified[k_mdl_last_mod_max]
Cached Last-Modified response value.
uint64_t content_hash
FNV-1a 64 of the fetched bytes.
char rel_path[k_mdl_relpath_max]
Path under the series directory.
uint64_t url_hash
FNV-1a 64 of the source URL.
uint16_t response_status
Most recent HTTP status; zero if legacy.
int64_t fetched_at
Most recent HTTP result time (epoch s).
One series' complete persistent state (declare at file scope).
char summary[k_mdl_summary_max]
Series synopsis.
uint16_t chapter_count
Chapters recorded.
char cover_path[k_mdl_relpath_max]
Local cover path.
char series_title[k_mdl_title_max]
Series title.
mdl_chapter_rec_t chapters[k_mdl_max_chapters]
Per-chapter coverage.
char series_url[k_mdl_url_max]
Series page URL.
char site_name[k_mdl_name_max]
Descriptor name.
mdl_state_reading_direction_t reading_direction
Page progression.
char language[k_mdl_language_max]
BCP-47 language tag.
uint16_t version
Schema version.
char artist[k_mdl_person_max]
Artist/illustrator.
uint32_t page_rec_count
Page records recorded.
mdl_page_rec_t pages[k_mdl_max_page_recs]
Per-page identities.
char site_host[k_mdl_host_max]
Site host.
char cover_url[k_mdl_url_max]
Remote cover URL.
char config_path[k_mdl_cfgpath_max]
Descriptor used.
char writer[k_mdl_person_max]
Writer/author.