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:
1 parent
19620a7957
commit
562cc44931
16 files changed
+2330
-108
No files matched your search
+37
-23
@@ -1,21 +1,24 @@
|
||||
/* ui/library/library.c — 图库根页(ACT_LIBRARY):
|
||||
*
|
||||
* TopAppBar(标题 + 返回 + ⚙ 位图动作)+ meow_listview 虚拟化目录列表。
|
||||
* 任意文件夹可浏览;无元数据但含 PNG/JPEG/BMP 的文件夹即一本书(含图目录=册)。
|
||||
* 行=缩略图 + 目录名 + 页数;目录由 logic/library 扫描并自然排序。
|
||||
* 任意文件夹可浏览;无元数据但含 PNG/JPEG/BMP 的文件夹即一本书(含图目录=册);
|
||||
* M11 起 .cbz/.zip 也作为「册」与文件夹并列(行=封面+文件名+页数,点入直接阅读)。
|
||||
* 行数据由 logic/library 扫描并自然排序;档案册页数/首图由 logic/reader 的 zip 页源提供。
|
||||
*
|
||||
* 流畅度(用户指令优先):
|
||||
* - 首帧只绘文字行:bind 不读盘;缩略图请求投递给后台线程(pthread)异步解码,
|
||||
* 完成后在 on_tick 补位并只 invalidate 列表视口;
|
||||
* - 缩略图 meow_img_downscale 到 96px,并落磁盘缓存 MeowData/<pkg>/cache
|
||||
* (BMP 原始缩略图,文件名含源文件 mtime+size,源文件变化自动失效;≥64 张按
|
||||
* mtime LRU 删除);
|
||||
* (BMP 原始缩略图,文件名含源 mtime+size,源文件变化自动失效;≥64 张按
|
||||
* mtime LRU 删除);档案册封面=档内首图,先解压到同一 cache(page_cache 管 LRU);
|
||||
* - 列表滚动用 meow_scroll_wrap:跟手 track + 松手 fling + 行高 snap。
|
||||
*
|
||||
* 绘制/颜色:SDK 注册表非保留绘制,全部取 meow_theme_get() 语义色;主题切换由 app.c 的
|
||||
* listener 整帧 invalidate。缩略图缓存只在列表重建时释放(避免池内悬垂指针),上限 24 张。
|
||||
*/
|
||||
#include "tuku_internal.h"
|
||||
#include "logic/reader/page_source.h"
|
||||
#include "logic/reader/page_cache.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
@@ -106,20 +109,10 @@ static void cache_init(void)
|
||||
const char *data = meow_config_dir();
|
||||
if (!data || !data[0]) return;
|
||||
|
||||
int n = snprintf(g_cache_dir, sizeof g_cache_dir, "%s", data);
|
||||
if (n <= 0 || n >= (int)sizeof g_cache_dir) { g_cache_dir[0] = '\0'; return; }
|
||||
|
||||
char *slash = strrchr(g_cache_dir, '/');
|
||||
if (slash && strcmp(slash, "/data") == 0) *slash = '\0';
|
||||
|
||||
size_t len = strlen(g_cache_dir);
|
||||
n = snprintf(g_cache_dir + len, sizeof g_cache_dir - len, "/cache");
|
||||
if (n <= 0 || n >= (int)(sizeof g_cache_dir - len)) { g_cache_dir[0] = '\0'; return; }
|
||||
|
||||
if (mkdir(g_cache_dir, 0755) != 0 && errno != EEXIST) {
|
||||
g_cache_dir[0] = '\0';
|
||||
return;
|
||||
}
|
||||
/* 缩略图缓存与解压页缓存同目录(MeowData/<pkg>/cache)。目录推导、建立与
|
||||
* 启动清孤儿统一交给 logic/reader/page_cache,避免两处目录规则分叉。 */
|
||||
if (mr_cache_init(data) != 0 || mr_cache_dir()[0] == '\0') return;
|
||||
snprintf(g_cache_dir, sizeof g_cache_dir, "%s", mr_cache_dir());
|
||||
g_cache_on = 1;
|
||||
}
|
||||
|
||||
@@ -220,12 +213,21 @@ static int write_bmp(const char *path, const meow_img *im)
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
/* 后台线程:缓存命中直接读;否则解码源图 → downscale → 落缓存。 */
|
||||
/* 后台线程:缓存命中直接读;否则解码源图 → downscale → 落缓存。
|
||||
* 档案册(M11)的 cover 是 .cbz/.zip 路径:先解压档内首图到 page_cache,
|
||||
* 再按普通图片路径解码;缩略图缓存键仍用档案本身(mtime/size 变化即失效)。 */
|
||||
static meow_img *thumb_load(const char *cover)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(cover, &st) != 0 || !S_ISREG(st.st_mode)) return NULL;
|
||||
|
||||
char extracted[MR_CACHE_FILE_MAX];
|
||||
const char *src = cover;
|
||||
if (mr_path_is_archive(cover)) {
|
||||
if (!mr_zip_cover_path(cover, extracted, (int)sizeof extracted)) return NULL;
|
||||
src = extracted;
|
||||
}
|
||||
|
||||
char cp[MR_PATH_MAX + 96];
|
||||
if (g_cache_on && g_cache_dir[0]) {
|
||||
cache_path(cp, (int)sizeof cp, cover, &st);
|
||||
@@ -239,7 +241,7 @@ static meow_img *thumb_load(const char *cover)
|
||||
}
|
||||
}
|
||||
|
||||
meow_img *im = meow_img_load_file(cover);
|
||||
meow_img *im = meow_img_load_file(src);
|
||||
if (!im) return NULL;
|
||||
meow_img *small = meow_img_downscale(im, MR_THUMB_DIM);
|
||||
if (small) { meow_img_free(im); im = small; }
|
||||
@@ -380,7 +382,11 @@ static void lib_bind(int idx, meow_lv_item *item, void *ctx)
|
||||
item->icon = im;
|
||||
} else {
|
||||
item->icon_kind = e->has_images ? MEOW_LIST_ICON_FILE : MEOW_LIST_ICON_FOLDER;
|
||||
if (e->cover[0]) thumb_enqueue(idx, e->cover);
|
||||
/* 文件夹册用缩略图源图;档案册没有图片文件,用档案路径(worker 解压首图)。 */
|
||||
const char *src = NULL;
|
||||
if (e->cover[0]) src = e->cover;
|
||||
else if (e->has_images && mr_path_is_archive(e->path)) src = e->path;
|
||||
if (src) thumb_enqueue(idx, src);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,7 +417,7 @@ static void list_rebuild(void)
|
||||
/* SDK 默认 container=0(扁平满幅+分隔线);显式开 MD3 容器模式:
|
||||
* 每行 surfaceContainer 圆角卡 + 行间空隙 + 卡内 tonal 选中。 */
|
||||
meow_listview_set_container(g_lv, 1);
|
||||
meow_listview_set_empty_text(g_lv, "No folders with PNG/JPEG/BMP");
|
||||
meow_listview_set_empty_text(g_lv, "No folders or CBZ books");
|
||||
|
||||
g_scroll = meow_scroll_wrap(g_lv, 1);
|
||||
if (g_scroll) meow_scroll_set_snap(g_scroll, MR_DEFAULT_ROW_H);
|
||||
@@ -446,12 +452,20 @@ static void reset_async(void)
|
||||
pthread_mutex_unlock(&g_lock);
|
||||
}
|
||||
|
||||
/* 书库扫描的档案册探测(M11 注入):logic/library 不依赖 zip 容器。 */
|
||||
static int cbz_archive_pages(const char *path, void *ud)
|
||||
{
|
||||
(void)ud;
|
||||
return mr_zip_count_pages(path);
|
||||
}
|
||||
|
||||
static void load_dir(const char *dir)
|
||||
{
|
||||
snprintf(g_cur_dir, sizeof g_cur_dir, "%s", dir);
|
||||
reset_async();
|
||||
thumbs_free();
|
||||
if (mr_library_scan(&g_lib, g_cur_dir, MR_LIB_MAX_DEPTH) < 0) {
|
||||
if (mr_library_scan_ex(&g_lib, g_cur_dir, MR_LIB_MAX_DEPTH,
|
||||
cbz_archive_pages, NULL) < 0) {
|
||||
mr_library_free(&g_lib);
|
||||
meow_log(MEOW_LOG_WARN, "library scan failed: %s", g_cur_dir);
|
||||
}
|
||||
|
||||
+25
-11
@@ -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; }
|
||||
|
||||
Reference in new issue
Block a user