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

+25 -11
View File
@@ -39,6 +39,7 @@
#include "tuku_internal.h"
#include "ui/reader/reader.h"
#include "logic/reader/page_source.h"
#include "logic/reader/page_cache.h"
#include "logic/reader/nav.h"
#include <pthread.h>
@@ -74,7 +75,7 @@
static mr_page_ctrl g_ctrl;
static char g_dir[MR_PATH_MAX];
static mr_dir_book g_db;
static mr_book_handle g_handle; /* 目录册或档案册(M11 统一开册) */
static mr_book g_book;
static int g_count; /* 0=空册/不可读 */
static int g_open_err;
@@ -765,7 +766,7 @@ static void reader_draw(meow_view *v, const meow_theme *th, void *ctx)
int w = g_vw, h = g_vh;
if (g_open_err) {
meow_ui_text_center(w / 2, h / 2 - 20, "无法打开目录", th->font_title, th->onSurface);
meow_ui_text_center(w / 2, h / 2 - 20, "无法打开此册", th->font_title, th->onSurface);
meow_ui_text_center(w / 2, h / 2 + 24, g_dir, th->font_body, th->onSurfaceVariant);
return;
}
@@ -1152,8 +1153,7 @@ static void open_book_reset_state(void)
mr_strip_free(&g_strip);
g_strip_ok = 0;
slots_free_all();
mr_book_destroy(&g_book);
mr_dir_book_free(&g_db);
mr_book_close_auto(&g_book, &g_handle);
if (g_slider) { meow_widget_free(g_slider); g_slider = NULL; }
g_gen++; /* 作废旧册的异步结果 */
g_count = 0;
@@ -1183,10 +1183,11 @@ static int open_book(const char *dir, int start_page, int mode)
g_mode = mr_mode_valid(mode) ? mode : MR_MODE_PAGED;
int maxdim = g_vw > g_vh ? g_vw : g_vh;
int n = mr_dir_book_init(&g_db, g_dir, maxdim,
reader_load_page, reader_unload_page, NULL);
/* 目录 → 目录册;.cbz/.zip → 档案册(M11)。两者同一个 mr_book 契约,
* 下面的翻页/条漫消费代码完全不区分 provider。 */
int n = mr_book_open_auto(&g_book, &g_handle, g_dir, maxdim,
reader_load_page, reader_unload_page, NULL);
if (n < 0) {
mr_dir_book_bind(&g_book, NULL);
g_open_err = 1;
worker_start();
update_title();
@@ -1195,7 +1196,6 @@ static int open_book(const char *dir, int start_page, int mode)
return 0;
}
g_count = n;
mr_dir_book_bind(&g_book, g_count > 0 ? &g_db : NULL);
int page = start_page < 0 ? g_count - 1 : start_page;
page = mr_nav_clamp_page(page, g_count);
@@ -1233,12 +1233,20 @@ static int open_book(const char *dir, int start_page, int mode)
return 1;
}
/* 册间衔接的档案册探测(M11):兄弟表 = 含图目录 ∪ 含图片页的 .cbz/.zip。 */
static int cbz_pages_probe(const char *path, void *ud)
{
(void)ud;
return mr_zip_count_pages(path);
}
static int book_switch(int delta)
{
if (delta == 0) return 0;
if (g_count <= 0) return 0;
char next[MR_PATH_MAX];
int r = mr_book_adjacent(g_dir, delta > 0 ? 1 : -1, next, (int)sizeof next);
int r = mr_book_adjacent_ex(g_dir, delta > 0 ? 1 : -1, next, (int)sizeof next,
cbz_pages_probe, NULL);
if (r != 1) {
toast(delta > 0 ? "已是最后一册" : "已是第一册");
return 0;
@@ -1770,6 +1778,13 @@ static void reader_page_enter(meow_view *v, void *ctx)
}
if (g_tb) meow_widget_set_rect(g_tb, 0, 0, g_vw, MR_TOP_BAR_H);
/* 档案册解压缓存(M11):MeowData/<pkg>/cache。library 页通常已初始化,
* 这里幂等兜底(进程重启后直接进阅读器、或交付环境只剩 reader 入口)。 */
{
const char *data = meow_config_dir();
if (data && data[0]) mr_cache_init(data);
}
/* 打开册:恢复该册上次的模式与页码(无记录 → 翻页模式第 1 页)。 */
int mode = mr_cont_get_mode(g_dir, MR_MODE_PAGED);
int page = mr_cont_get_page(g_dir, 0);
@@ -1801,8 +1816,7 @@ static void reader_page_leave(meow_view *v, void *ctx)
mr_strip_free(&g_strip);
g_strip_ok = 0;
slots_free_all();
mr_book_destroy(&g_book);
mr_dir_book_free(&g_db);
mr_book_close_auto(&g_book, &g_handle);
if (g_slider) { meow_widget_free(g_slider); g_slider = NULL; }
if (g_tb) { meow_widget_free(g_tb); g_tb = NULL; }