feat(tuku): CBZ 档案册集成——档案页源/书库列册/解压缓存

- logic/reader/archive_source.c:档案册页源实现 M6 mr_book 契约
  (mz_open/mz_count/mz_entry_at/mz_read_alloc),条目自然序 + 图片后缀 +
  路径安全(`..`/绝对路径/反斜杠/隐藏段/内嵌 NUL)+ 单页尺寸上限过滤;
  meta 按需解压只读图片头(不落缓存,结果按页记忆);mr_book_open_auto
  统一目录册/档案册开册,翻页与条漫两套消费代码零改动。
- logic/reader/page_cache.c/h:解压页落 MeowData/<pkg>/cache,LRU 64 页或
  32MiB,启动清 mrpage_* 孤儿,写入 .part + rename;与缩略图缓存同目录、
  前缀隔离;互斥量保护,解码/缩略图线程可并发。
- logic/library.c/h:扫描直接子文件里的 .cbz/.zip 成册(行=文件名+页数,
  点入直接阅读);档案探测由调用方注入(logic 不依赖 zip 容器),不注入时
  与 M7 行为逐字一致;册间衔接兄弟表=含图目录∪含页档案册,同列自然序。
  条目构造/副标题/角标/兄弟判据拆成小函数,便于 M13 书库重写拼接。
- ui/library:档案册行 + 封面缩略图(解压档内首图)、扫描注入探测、缓存
  目录统一由 page_cache 推导(一份规则)。
- ui/reader:open_book 走 mr_book_open_auto;跨册衔接纳入档案册。
- build_tuku.sh 补 zip/archive/cache 源与 sysroot zlib 头(zip.c include
  <zlib.h>);run_tests.sh 纳入 zip+cbz 用例;run_webtoon_tests.sh 的宿主
  集成构建补新源与 -lz;.gitignore 同步测试产物。
- test/test_cbz.c(267 项断言)+ make_cbz_samples.py 真实 cbz 夹具:
  列档/自然序/解压字节 CRC/路径安全/坏档与 zip 炸弹容错/LRU 与 32MiB 上限/
  启动清孤儿/续读角标/封面首图。
This commit is contained in:
AskaEth committed 2026-11-05 11:58:12 +08:00
1 parent 19620a7957
commit 562cc44931
16 files changed
+2330 -108

No files matched your search

+78
View File
@@ -18,6 +18,7 @@
#define MR_PAGE_SOURCE_H
#include "logic/library.h" /* mr_path_is_image / mr_natural_cmp / MR_PATH_MAX / MR_NAME_MAX */
#include <stddef.h> /* size_t(mr_probe_size_mem) */
#ifdef __cplusplus
extern "C" {
@@ -84,6 +85,83 @@ void mr_dir_book_bind(mr_book *b, mr_dir_book *db);
/* 纯 C 头解析:取图片原始像素尺寸(不解码像素)。
* PNG(IHDR)/ JPEG(SOF0-15)/ BMP(BITMAPINFOHEADER+)。1=已知,0=未知。 */
int mr_probe_size(const char *path, int *w, int *h);
/* 同上,但从内存缓冲探测(档案页解压后不需要落盘再读一遍)。 */
int mr_probe_size_mem(const void *buf, size_t len, int *w, int *h);
/* ============================================================ */
/* 档案册(M11):.cbz / .zip 当一本书 */
/* ------------------------------------------------------------
* 目录册之外的第二种 mr_book 实现,契约完全相同。条目名按 zip 原始字节
* 透传(通常 UTF-8):只收「图片后缀 + 路径安全 + 非隐藏 + 非目录」的条目,
* 跨子目录按 mr_natural_cmp 自然排序。open(i) 先把条目解压进
* logic/reader/page_cache(磁盘 LRU),再用注入的解码回调按路径解码——
* 与目录册走同一条 UI 解码/预算路径,翻页与条漫两种模式零改动复用。
*
* 条目名永远不会被拼成文件系统路径(缓存文件名是哈希),路径安全过滤是
* 纵深防御:拒绝绝对路径、'..' 段、反斜杠与内嵌 NUL。
* ============================================================ */
#define MR_ZIP_MAX_ENTRY_NAME 1024 /* 超长条目名跳过(防病态包) */
#define MR_ZIP_PAGE_MAX_BYTES (64LL * 1024 * 1024) /* 单页解压上限(防 zip 炸弹) */
typedef struct mr_zip_book {
char path[MR_PATH_MAX];
void *arch; /* mz_archive*(zip 层句柄;不对外暴露) */
void *lock; /* pthread_mutex_t*:mz_read* 串行化(open/meta 共用) */
int *order; /* 页 i → zip 条目下标(堆分配) */
int *meta_w; /* 页原始宽/高(按需解压头探测;NULL=未探测) */
int *meta_h;
int count;
int maxdim;
long long mtime; /* 档案 mtime/size:解压缓存键的一部分 */
long long size;
mr_book_load_fn load; /* 可为 NULL(只取封面/页路径时不需要解码) */
mr_book_unload_fn unload;
void *ud;
} mr_zip_book;
/* 打开档案并建立页表。返回页数(>=0);参数非法/坏档/空档案不可读返回 -1
* (zb 已清空、可安全 free)。0 页的合法档案返回 0(zb 仍持有打开的 archive)。 */
int mr_zip_book_init(mr_zip_book *zb, const char *path, int maxdim,
mr_book_load_fn load, mr_book_unload_fn unload, void *ud);
void mr_zip_book_free(mr_zip_book *zb);
/* 把档案册绑定到通用 mr_book(zb 生命周期由调用方持有)。 */
void mr_zip_book_bind(mr_book *b, mr_zip_book *zb);
/* 第 index 页在 zip 内的条目名(原始字节);越界输出空串。 */
void mr_zip_book_page_name(const mr_zip_book *zb, int index, char *out, int outsz);
/* 确保第 index 页已解压到缓存并返回缓存文件路径;越界/坏档/缓存不可用 -1。 */
int mr_zip_book_page_path(mr_zip_book *zb, int index, char *out, int outsz);
/* 只数页(书库扫描/邻册判断用;不解压、不建缓存)。0=不是档案或没有图片页。 */
int mr_zip_count_pages(const char *path);
/* 档案封面:档内自然序第一张图解到缓存,路径写入 out。1=成功。 */
int mr_zip_cover_path(const char *path, char *out, int outsz);
/* 条目是否算一页(图片后缀 + 路径安全 + 非隐藏 + 非目录)。1=是。 */
int mr_zip_entry_is_page(const char *name, unsigned name_len);
/* ============================================================ */
/* 统一开册(目录册 / 档案册) */
/* ------------------------------------------------------------
* 书库点入的行既可能是目录也可能是 .cbz/.zip;阅读器只调这里,不关心 provider。
* 句柄由调用方提供(阅读器用静态变量),生命周期 = 打开到 close_auto。
* ============================================================ */
#define MR_BOOK_KIND_NONE 0
#define MR_BOOK_KIND_DIR 1
#define MR_BOOK_KIND_ZIP 2
typedef struct mr_book_handle {
int kind;
union {
mr_dir_book dir;
mr_zip_book zip;
} u;
} mr_book_handle;
/* 按路径自动选 provider 并绑定 b。返回页数(>=0)或 -1(打不开)。
* 页数为 0 时 b 不绑定(空册),句柄仍可安全 mr_book_close_auto。 */
int mr_book_open_auto(mr_book *b, mr_book_handle *h, const char *path, int maxdim,
mr_book_load_fn load, mr_book_unload_fn unload, void *ud);
/* 释放 provider(先 mr_book_destroy 再释放各自索引);可重复调用。 */
void mr_book_close_auto(mr_book *b, mr_book_handle *h);
#ifdef __cplusplus
}