ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_export.c
Go to the documentation of this file.
1
14
15#include "mdl_export.h"
16
17#include <ctype.h>
18#include <stdio.h>
19#include <string.h>
20
21#include "mdl_export_internal.h"
22#include "ra8_attributes.h"
23
31
33{
34 if ((s == nullptr) || (strcmp(s, "loose") == 0)) {
35 return k_mdl_format_loose;
36 }
37 if (strcmp(s, "cbz") == 0) {
38 return k_mdl_format_cbz;
39 }
40 if (strcmp(s, "cbt") == 0) {
41 return k_mdl_format_cbt;
42 }
43 if (strcmp(s, "cbt.gz") == 0) {
45 }
46 if (strcmp(s, "epub") == 0) {
47 return k_mdl_format_epub;
48 }
49 if (strcmp(s, "jof") == 0) {
50 return k_mdl_format_jof;
51 }
52 if (strcmp(s, "rabook") == 0) {
54 }
56}
57
59{
60 switch (fmt) {
62 return "cbz";
64 return "cbt";
66 return "cbr";
68 return "cbt.xz";
70 return "cbt.gz";
72 return "epub";
74 return "jof";
76 return "rabook";
79 default:
80 return "";
81 }
82}
83
85{
86 /* JOF is inherently per-page: one `.jof` band atlas is written beside each
87 * source image, so a chapter is a directory of atlases rather than a single
88 * container file at out_path. Every other archive format produces one file.
89 */
90 return fmt == k_mdl_format_jof;
91}
92
108RA8_INTERNAL static bool internal_ends_with_ci(const char* name, const char* suffix)
109{
110 const size_t nl = strlen(name);
111 const size_t sl = strlen(suffix);
112 if (sl > nl) {
113 return false;
114 }
115 const char* tail = name + (nl - sl);
116 for (size_t i = 0U; i < sl; ++i) {
117 if (tolower((unsigned char)tail[i]) != tolower((unsigned char)suffix[i])) {
118 return false;
119 }
120 }
121 return true;
122}
123
144RA8_INTERNAL static bool internal_is_page_image(const char* name)
145{
146 static const char* const k_img_exts[] = {".jpg", ".jpeg", ".png", ".webp", ".gif", ".bmp"};
147 for (size_t i = 0U; i < (sizeof(k_img_exts) / sizeof(k_img_exts[0])); ++i) {
148 if (internal_ends_with_ci(name, k_img_exts[i])) {
149 return true;
150 }
151 }
152 return false;
153}
154
168RA8_INTERNAL static void internal_sort_pages(char names[][k_name_max], size_t count)
169{
170 for (size_t i = 1U; i < count; ++i) {
171 char held[k_name_max];
172 size_t cursor = i;
173 memcpy(held, names[i], sizeof(held));
174 while ((cursor > 0U) && (strcmp(names[cursor - 1U], held) > 0)) {
175 memcpy(names[cursor], names[cursor - 1U], k_name_max);
176 --cursor;
177 }
178 memcpy(names[cursor], held, sizeof(held));
179 }
180}
181
202 const char* dir,
204 fw_fs_dir_t* directory)
205{
206 fw_fs_caps_t caps = {};
207 ra8_err_t err = fw_fs_get_caps(storage->fs, &caps);
208 void* dir_workspace = nullptr;
209 if (err == k_ra8_ok) {
210 dir_workspace =
212 if (dir_workspace == nullptr) {
214 }
215 }
216 if (err == k_ra8_ok) {
217 err = fw_fs_dir_open(&storage->fs->names,
218 dir,
219 directory,
220 dir_workspace,
222 }
223 return err;
224}
225
247 const char* dir,
248 char names[][k_name_max],
249 size_t cap,
251 size_t* out_count)
252{
253 const size_t floor = ws->used;
254 fw_fs_dir_t directory = {};
255 ra8_err_t err = internal_list_pages_open_dir(storage, dir, ws, &directory);
256 size_t count = 0U;
257 bool entry = true;
258 while ((err == k_ra8_ok) && entry) {
259 fw_fs_dirent_value_t value = {};
260 err = fw_fs_dir_next(&directory, &value, &entry);
261 if ((err == k_ra8_ok) && entry && (value.name[0] != '.') &&
263 if (value.type != k_fw_fs_node_file) {
265 } else if ((value.name_bytes >= (uint16_t)k_name_max) || (count >= cap)) {
267 } else {
268 memcpy(names[count], value.name, (size_t)value.name_bytes + 1U);
269 ++count;
270 }
271 }
272 }
273 if (directory.is_open) {
274 const ra8_err_t closed = fw_fs_dir_close(&directory);
275 if ((err == k_ra8_ok) && (closed != k_ra8_ok)) {
276 err = closed;
277 }
278 }
279 ws->used = floor;
280 if (err == k_ra8_ok) {
281 internal_sort_pages(names, count);
282 *out_count = count;
283 }
284 return err;
285}
286
302{
303 uint64_t key = value->year;
304 key = (key * k_export_month_radix) + value->month;
305 key = (key * 32U) + value->day;
306 key = (key * k_export_hours_per_day) + value->hour;
307 key = (key * k_export_minutes_per_hour) + value->minute;
308 return (key * k_export_seconds_per_minute) + value->second;
309}
310
329 mdl_export_meta_t* meta,
330 const char* dir,
331 const char names[][k_name_max],
332 size_t count)
333{
334 fw_fs_timestamp_t latest = {};
335 uint64_t latest_key = 0U;
336 for (size_t i = 0U; i < count; ++i) {
337 char path[k_fw_fs_path_cap];
338 if (priv_mdl_export_path_join(path, sizeof(path), dir, names[i]) != k_ra8_ok) {
339 return false;
340 }
341 fw_fs_stat_t stat = {};
342 if ((fw_fs_stat(&storage->fs->names, path, &stat) != k_ra8_ok) || !stat.exists ||
343 (stat.type != k_fw_fs_node_file)) {
344 return false;
345 }
346 if (stat.modified.valid) {
347 const uint64_t key = internal_timestamp_key(&stat.modified.value);
348 if (!latest.valid || (key > latest_key)) {
349 latest = stat.modified;
350 latest_key = key;
351 }
352 }
353 }
354 if (!latest.valid) {
355 return snprintf(meta->modified, sizeof(meta->modified), "1970-01-01T00:00:00Z") > 0;
356 }
357 const fw_fs_datetime_t* value = &latest.value;
358 if (latest.utc_offset_valid && (value->utc_offset_min != 0)) {
359 const int offset = value->utc_offset_min;
360 const int absolute = (offset < 0) ? -offset : offset;
361 return snprintf(meta->modified,
362 sizeof(meta->modified),
363 "%04u-%02u-%02uT%02u:%02u:%02u%c%02d:%02d",
364 value->year,
365 value->month,
366 value->day,
367 value->hour,
368 value->minute,
369 value->second,
370 (offset < 0) ? '-' : '+',
371 absolute / k_export_minutes_per_hour,
372 absolute % k_export_minutes_per_hour) > 0;
373 }
374 return snprintf(meta->modified,
375 sizeof(meta->modified),
376 "%04u-%02u-%02uT%02u:%02u:%02uZ",
377 value->year,
378 value->month,
379 value->day,
380 value->hour,
381 value->minute,
382 value->second) > 0;
383}
384
409 mdl_format_t fmt,
410 const char* dir,
411 char names[][k_name_max],
412 size_t count,
413 mdl_export_output_t* output,
414 const mdl_export_meta_t* meta,
416{
417 switch (fmt) {
418 case k_mdl_format_cbz:
419 return priv_mdl_export_cbz(storage, dir, names, count, output, meta, ws);
420 case k_mdl_format_cbt:
421 return priv_mdl_export_tar(storage, dir, names, count, output, meta);
423 return priv_mdl_export_tar_gzip(storage, dir, names, count, output, meta, ws);
425 return priv_mdl_export_epub(storage, dir, names, count, output, meta, ws);
426 case k_mdl_format_cbr:
430 case k_mdl_format_jof:
433 default:
435 }
436}
437
467 mdl_format_t fmt,
468 const char* dir,
469 char names[][k_name_max],
470 size_t count,
471 const char* out_path,
472 const mdl_export_meta_t* meta,
474{
475 mdl_export_output_t output = {};
476 ra8_err_t rc = priv_mdl_export_output_begin(&output, storage, out_path, fmt);
477 if (rc == k_ra8_ok) {
478 rc = internal_export_dispatch(storage, fmt, dir, names, count, &output, meta, ws);
479 }
480 if ((rc != k_ra8_ok) && output.writer.transaction.active) {
481 const ra8_err_t aborted = priv_mdl_export_output_abort(&output);
482 return (aborted == k_ra8_ok) ? rc : aborted;
483 }
484 bool published = false;
485 return (rc == k_ra8_ok) ? priv_mdl_export_output_commit(&output, ws, &published) : rc;
486}
487
510 const char* chapter_dir,
511 char names[][k_name_max],
512 size_t count,
513 const mdl_export_meta_t* meta,
514 mdl_export_meta_t* resolved)
515{
516 if (meta != nullptr) {
517 *resolved = *meta;
518 } else {
519 mdl_meta_init(resolved);
520 }
521 const ra8_err_t source_rc = priv_mdl_export_validate_source_url(resolved->source_url);
522 if (source_rc != k_ra8_ok) {
523 return source_rc;
524 }
525 if ((resolved->modified[0] == '\0') &&
526 !internal_metadata_set_page_timestamp(storage, resolved, chapter_dir, names, count)) {
527 return k_ra8_fail;
528 }
529 return k_ra8_ok;
530}
531
533 mdl_format_t fmt,
534 const char* chapter_dir,
535 const char* out_path,
536 const mdl_export_meta_t* meta,
538{
539 if ((storage == nullptr) || (chapter_dir == nullptr) || (out_path == nullptr) ||
540 (fmt == k_mdl_format_loose) || (fmt == k_mdl_format_invalid) || (ws == nullptr) ||
541 (ws->data == nullptr)) {
543 }
544 ws->used = 0U;
545 if ((fmt == k_mdl_format_cbr) || (fmt == k_mdl_format_cbt_xz)) {
547 }
548
549 ws->high_water = 0U;
550 char (*names)[k_name_max] = mdl_export_workspace_take(ws,
551 (size_t)k_max_pages * (size_t)k_name_max,
552 _Alignof(char[k_name_max]));
553 if (names == nullptr) {
555 }
556
557 size_t count = 0U;
558 const ra8_err_t listed =
559 internal_list_pages(storage, chapter_dir, names, (size_t)k_max_pages, ws, &count);
560 if (listed != k_ra8_ok) {
561 return listed;
562 }
563 if (count == 0U) {
564 return k_ra8_err_empty;
565 }
566 mdl_export_meta_t resolved = {};
567 const ra8_err_t meta_rc =
568 internal_resolve_export_metadata(storage, chapter_dir, names, count, meta, &resolved);
569 if (meta_rc != k_ra8_ok) {
570 return meta_rc;
571 }
572
573 if (fmt == k_mdl_format_jof) {
574 /* JOF writes one `.jof` sibling per page into chapter_dir; out_path names
575 * no single container (see mdl_format_is_dir_output), so there is no single
576 * file to rename into place -- priv_mdl_export_jof commits each page itself. */
577 return priv_mdl_export_jof(storage, chapter_dir, names, count, ws);
578 }
579 if (fmt == k_mdl_format_rabook) {
580 return priv_mdl_export_rabook(storage, chapter_dir, names, count, out_path, &resolved, ws);
581 }
582 return internal_export_transaction(storage,
583 fmt,
584 chapter_dir,
585 names,
586 count,
587 out_path,
588 &resolved,
589 ws);
590}
591
593 mdl_format_t fmt,
594 const char* chapter_dir,
595 const char* out_path,
597{
599 if (chapter_dir != nullptr) {
600 (void)mdl_meta_load_dir(storage, &meta, chapter_dir);
601 } else {
602 mdl_meta_init(&meta);
603 }
604 return mdl_export_chapter_meta_ws(storage, fmt, chapter_dir, out_path, &meta, ws);
605}
ra8_err_t fw_fs_get_caps(const fw_fs_t *fs, fw_fs_caps_t *out)
Copy the immutable capability snapshot from a complete binding.
Definition fw_if_fs.c:468
ra8_err_t fw_fs_stat(const fw_fs_namespace_t *names, const char *path, fw_fs_stat_t *out)
Query a path; a miss is success with out->exists == false.
Definition fw_if_fs.c:480
ra8_err_t fw_fs_dir_next(fw_fs_dir_t *directory, fw_fs_dirent_value_t *out, bool *out_entry)
Copy one stable directory entry from an open cursor.
ra8_err_t fw_fs_dir_close(fw_fs_dir_t *directory)
Close and consume an open directory cursor, including on close error.
ra8_err_t fw_fs_dir_open(const fw_fs_namespace_t *names, const char *path, fw_fs_dir_t *directory, void *workspace, uint32_t workspace_size)
Open one directory cursor into caller-owned backend workspace.
@ k_fw_fs_node_file
Regular byte stream.
@ k_fw_fs_path_cap
Largest portable path including its NUL.
static uint64_t internal_timestamp_key(const fw_fs_datetime_t *value)
Map one civil timestamp to a stable ordering key.
Definition mdl_export.c:301
bool mdl_format_is_dir_output(mdl_format_t fmt)
Whether fmt writes per-page sibling files rather than one container.
Definition mdl_export.c:84
static ra8_err_t internal_export_dispatch(mdl_storage_t *storage, mdl_format_t fmt, const char *dir, char names[][k_name_max], size_t count, mdl_export_output_t *output, const mdl_export_meta_t *meta, mdl_export_workspace_t *ws)
Dispatch one selected container writer.
Definition mdl_export.c:408
static void internal_sort_pages(char names[][k_name_max], size_t count)
Sort a bounded page-name table lexically in place.
Definition mdl_export.c:168
static bool internal_ends_with_ci(const char *name, const char *suffix)
Test a filename suffix without ASCII case sensitivity.
Definition mdl_export.c:108
mdl_format_t mdl_format_from_str(const char *s)
Map a --format string to a container kind.
Definition mdl_export.c:32
static ra8_err_t internal_list_pages_open_dir(mdl_storage_t *storage, const char *dir, mdl_export_workspace_t *ws, fw_fs_dir_t *directory)
Open the chapter directory cursor into workspace-backed state.
Definition mdl_export.c:201
const char * mdl_format_ext(mdl_format_t fmt)
File extension (without dot) for a format, e.g.
Definition mdl_export.c:58
static bool internal_metadata_set_page_timestamp(mdl_storage_t *storage, mdl_export_meta_t *meta, const char *dir, const char names[][k_name_max], size_t count)
Derive a deterministic UTC modified timestamp from source pages.
Definition mdl_export.c:328
ra8_err_t mdl_export_chapter_meta_ws(mdl_storage_t *storage, mdl_format_t fmt, const char *chapter_dir, const char *out_path, const mdl_export_meta_t *meta, mdl_export_workspace_t *ws)
Package a chapter with explicit metadata and caller-owned workspace.
Definition mdl_export.c:532
mdl_export_time_radix_t
Mixed-radix civil timestamp geometry used for stable ordering.
Definition mdl_export.c:25
@ k_export_month_radix
One-based month field radix.
Definition mdl_export.c:26
@ k_export_seconds_per_minute
Civil seconds per minute.
Definition mdl_export.c:29
@ k_export_minutes_per_hour
Civil minutes per hour.
Definition mdl_export.c:28
@ k_export_hours_per_day
Civil hours per day.
Definition mdl_export.c:27
static ra8_err_t internal_resolve_export_metadata(mdl_storage_t *storage, const char *chapter_dir, char names[][k_name_max], size_t count, const mdl_export_meta_t *meta, mdl_export_meta_t *resolved)
Resolve and validate metadata for one chapter export.
Definition mdl_export.c:509
static ra8_err_t internal_export_transaction(mdl_storage_t *storage, mdl_format_t fmt, const char *dir, char names[][k_name_max], size_t count, const char *out_path, const mdl_export_meta_t *meta, mdl_export_workspace_t *ws)
Build out_path through one storage transaction.
Definition mdl_export.c:466
static bool internal_is_page_image(const char *name)
True if name is a raster page image a reader engine can decode.
Definition mdl_export.c:144
static ra8_err_t internal_list_pages(mdl_storage_t *storage, const char *dir, char names[][k_name_max], size_t cap, mdl_export_workspace_t *ws, size_t *out_count)
List and sort regular chapter image entries through a portable cursor.
Definition mdl_export.c:246
ra8_err_t mdl_export_chapter_ws(mdl_storage_t *storage, mdl_format_t fmt, const char *chapter_dir, const char *out_path, mdl_export_workspace_t *ws)
Package a chapter after auto-loading bounded local metadata.
Definition mdl_export.c:592
Package a downloaded chapter folder into a reader-openable container.
void * mdl_export_workspace_take(mdl_export_workspace_t *ws, size_t bytes, size_t alignment)
Reserve aligned bytes from an exporter arena.
struct mdl_export_workspace mdl_export_workspace_t
Caller-owned bounded arena for all exporter scratch state.
ra8_err_t mdl_meta_load_dir(mdl_storage_t *storage, mdl_export_meta_t *meta, const char *dir)
Load metadata from a directory by looking for metadata files.
void mdl_meta_init(mdl_export_meta_t *meta)
Initialise a metadata struct to empty/default values.
ra8_err_t priv_mdl_export_epub(mdl_storage_t *storage, const char *dir, char names[][k_name_max], size_t count, mdl_export_output_t *output, const mdl_export_meta_t *meta, mdl_export_workspace_t *ws)
Package chapter pages into a valid fixed-layout EPUB3.
Module-private contracts shared by chapter export writers.
ra8_err_t priv_mdl_export_rabook(mdl_storage_t *storage, const char *directory, char names[][k_name_max], size_t count, const char *destination, const mdl_export_meta_t *meta, mdl_export_workspace_t *workspace)
Compile one page directory into a strict chunked RABOOK artifact.
ra8_err_t priv_mdl_export_cbz(mdl_storage_t *storage, const char *dir, char names[][k_name_max], size_t count, mdl_export_output_t *output, const mdl_export_meta_t *meta, mdl_export_workspace_t *ws)
Write a CBZ into a caller-owned staged output.
ra8_err_t priv_mdl_export_tar_gzip(mdl_storage_t *storage, const char *dir, char names[][k_name_max], size_t count, mdl_export_output_t *output, const mdl_export_meta_t *meta, mdl_export_workspace_t *ws)
Write a gzip-wrapped CBT archive.
ra8_err_t priv_mdl_export_jof(mdl_storage_t *storage, const char *dir, const char names[][k_name_max], size_t count, mdl_export_workspace_t *ws)
Convert every page in a chapter directory to a sibling .jof atlas.
ra8_err_t priv_mdl_export_validate_source_url(const char *url)
Validate an optional bounded source-attribution URL.
@ k_max_pages
Maximum page entries per chapter.
@ k_name_max
Maximum entry-name bytes.
ra8_err_t priv_mdl_export_tar(mdl_storage_t *storage, const char *dir, char names[][k_name_max], size_t count, mdl_export_output_t *output, const mdl_export_meta_t *meta)
Write an uncompressed CBT tar archive.
ra8_err_t priv_mdl_export_path_join(char *out, size_t capacity, const char *directory, const char *leaf)
Join a canonical directory and leaf without truncation.
ra8_err_t priv_mdl_export_output_abort(mdl_export_output_t *output)
Abort one unpublished export stage, retaining cleanup failure.
ra8_err_t priv_mdl_export_output_commit(mdl_export_output_t *output, mdl_export_workspace_t *workspace, bool *out_published)
Structurally validate and publish one completed export stage.
ra8_err_t priv_mdl_export_output_begin(mdl_export_output_t *output, mdl_storage_t *storage, const char *destination, mdl_format_t format)
Bind a validated publication transaction to one destination.
mdl_format_t
Artifact format selected by a media-download composition root.
Definition mdl_format.h:35
@ k_mdl_format_cbz
ZIP of images (.cbz).
Definition mdl_format.h:37
@ k_mdl_format_invalid
Unrecognized or unsupported selection.
Definition mdl_format.h:45
@ k_mdl_format_loose
Leave loose page images.
Definition mdl_format.h:36
@ k_mdl_format_cbr
Reserved RAR output.
Definition mdl_format.h:39
@ k_mdl_format_epub
Fixed-layout EPUB (.epub).
Definition mdl_format.h:42
@ k_mdl_format_cbt_gz
Gzip-compressed tar (.cbt.gz).
Definition mdl_format.h:41
@ k_mdl_format_cbt_xz
Reserved xz-compressed tar output.
Definition mdl_format.h:40
@ k_mdl_format_rabook
Chunked reader-native book (.rabook).
Definition mdl_format.h:44
@ k_mdl_format_jof
Native JOF tile atlas per page (.jof).
Definition mdl_format.h:43
@ k_mdl_format_cbt
Tar of images (.cbt).
Definition mdl_format.h:38
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_not_supported
Requested feature not compiled in, not wired, or not supported by this MCU variant.
Definition ra8_err.h:180
@ k_ra8_err_empty
Container empty – nothing to retrieve.
Definition ra8_err.h:222
@ k_ra8_fail
Generic unspecified failure.
Definition ra8_err.h:133
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ 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 strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
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.
Static properties and workspace requirements of one bound port.
uint8_t directory_workspace_align
Required directory-state alignment.
uint32_t directory_workspace_bytes
State bytes required by dir open.
Backend-neutral civil time with nanosecond precision.
uint16_t year
Full civil year.
int16_t utc_offset_min
Offset from UTC when the validity flag is set.
uint8_t month
Month, 1..12.
uint8_t second
Second, 0..59.
uint8_t day
Day, 1..31.
uint8_t hour
Hour, 0..23.
uint8_t minute
Minute, 0..59.
Caller-owned open directory cursor; fields are private to the facade.
bool is_open
Facade lifecycle guard.
Stable caller-owned value returned by fw_fs_dir_next.
uint16_t name_bytes
Bytes excluding the NUL.
fw_fs_node_type_t type
Entry kind.
char name[k_fw_fs_path_cap]
Copied NUL-terminated leaf.
Result of a portable metadata query.
bool exists
False means a clean lookup miss.
fw_fs_node_type_t type
Kind of node at the path.
fw_fs_timestamp_t modified
Content modification time, when supported.
fw_fs_namespace_t names
Namespace operations.
One portable timestamp and independent availability facts.
fw_fs_datetime_t value
Decoded civil date and time.
bool utc_offset_valid
True when utc_offset_min is known.
bool valid
True when the field is meaningful.
bool active
Begin succeeded.
Rich series and chapter metadata for export containers (ComicInfo.xml, EPUB OPF).
Definition mdl_export.h:96
char modified[k_mdl_meta_date_max]
Deterministic ISO-8601 modified time.
Definition mdl_export.h:109
char source_url[k_mdl_meta_url_max]
Validated absolute HTTP(S) source URL.
Definition mdl_export.h:103
One validated staged publication, including random ZIP backfill.
mdl_storage_txn_t writer
Active storage transaction.
size_t high_water
Largest used value observed.
Definition mdl_export.h:221
size_t used
Current allocation high edge.
Definition mdl_export.h:220
uint8_t * data
Writable arena bytes.
Definition mdl_export.h:218
One non-reentrant downloader filesystem dependency bundle.
Definition mdl_storage.h:40
const fw_fs_t * fs
Injected portable filesystem.
Definition mdl_storage.h:41
fw_fs_transaction_t transaction
Active private filesystem stage.
Definition mdl_storage.h:60