图库: 书库改文件管理器模型 + 关闭缩略图 + 根锁定/无卡容错(M18)
用户反馈四件事一起改: - 「目录里有一张图就被当成最终目录」→ 取消「目录含图=一本书」全部链路 (探测/页数/封面/续读角标/懒算 worker/sr2 补位),列表 = 目录 + 图片 + .cbz/.zip(目录优先、组内自然序)。点目录=进入(空目录也进),点图片= mr_reader_open_dir_at(当前目录, 图片自然序下标),点档案册=打开档案册。 - 「文件夹浏览特别卡」→ 删除缩略图线程/磁盘 bmp 缓存/LRU/解码路径与行内 缩略图;首帧只做一次 opendir/readdir+lstat,副标题(类型·大小)扫描期 生成一次,行合成/滚动/点击零 IO 零格式化;去掉逐行 snap(自由滚动手感)。 2000 项目录扫描 ~2.8ms(opendir=1, lstat=2000),行合成 2.78ms/200 万次 (宿主实测;旧实现 2000 项 entry 含 512B cover,内存 2.7MB→1.7MB)。 - 「不要访问 SD 卡上级目录」→ 浏览根锁定 MR_ROOT_DIR:删除越根的顶栏 「上级」/library_go_parent,根隐藏顶栏返回,左缘返回到根=退出 App。 - 「没有 SD 卡不要报错」→ 根不可访问(未插卡/挂载失败/根是文件)时友好空态 「未检测到 SD 卡」+ 顶栏刷新/重进页面重试;子目录不可读才弹提示横幅。 集成陷阱:书库图片下标与阅读器目录册共享同一套过滤器(隐藏项/图片扩展名/ 普通文件/符号链接)与 mr_natural_cmp,test_browse_lazy 直接对比 mr_dir_book_init 顺序,test_open_book_host 断言「点 img10 就开 img10」。 reader 侧 M17 遗留:mr_reader_open_dir_at 在阅读页已激活时先 pop 再 push, 走完整 leave/enter(SDK 对同 view 重复 push 是 no-op 且会压重复 activity)。 回归:run_tests.sh / run_webtoon_tests.sh 全 PASS;交叉构建零警告。
This commit is contained in:
1 parent
d07b99056f
commit
64336f95d9
16 files changed
+940
-1927
No files matched your search
@@ -1,11 +1,14 @@
|
||||
/* test_open_book_host.c — 书库点击 → 阅读器打开的宿主集成回归(需 SDK 源码树)。
|
||||
*
|
||||
* 复现用户报告:「父文件夹有封面图片、子文件夹也有图片,点开父文件夹报无法打开此册」。
|
||||
* 用合成夹具(真实 BMP 当封面/子图,不读用户相册)走完整链路:
|
||||
* library 列表 → 行点击 → mr_reader_open_dir → reader on_enter → mr_book_open_auto
|
||||
* 并断言父目录以「书」打开(count=1)且没有进入 open_err。
|
||||
* M18 文件管理器模型:点目录=进入;点图片=从该图开始读(mr_reader_open_dir_at(目录, 下标));
|
||||
* 点档案册=打开档案册。本测试用合成夹具(真实 BMP,不读用户相册)走完整链路:
|
||||
* library 列表 → 点图片行 → mr_reader_open_dir_at → reader on_enter → mr_book_open_auto
|
||||
* 并断言「点哪张就开哪张」:阅读器当前页 == 该书库行在目录图片自然序里的下标。
|
||||
*
|
||||
* 另覆盖:封面图很大时打开仍成功(打开只枚举,不解码);同目录二次打开/离开重开。
|
||||
* 另覆盖:
|
||||
* - 目录行只进目录,不打开阅读器;
|
||||
* - 阅读页已在本页时再次 mr_reader_open_dir_at 能换册/换页重进(SDK 忽略重复 push 的坑);
|
||||
* - 离开阅读器回书库后再次点图(同目录二次打开)。
|
||||
*
|
||||
* 编译:见 test/run_tests.sh(与 test_mode_switch_host 同口径)。
|
||||
*/
|
||||
@@ -25,7 +28,6 @@ void library_test_set_root(const char *root);
|
||||
const char *library_test_dir(void);
|
||||
int library_test_rows(void);
|
||||
int library_test_src(int idx, meow_list_item *item);
|
||||
int library_test_worker_on(void);
|
||||
|
||||
/* ui/reader/reader.c 的测试接缝(-DMR_READER_HOST_TEST) */
|
||||
int mr_reader_ui_mode(void);
|
||||
@@ -116,22 +118,6 @@ static int rm_rf(const char *path)
|
||||
return rmdir(path);
|
||||
}
|
||||
|
||||
static void wait_ready(int ms)
|
||||
{
|
||||
for (int t = 0; t < ms / 10; t++) {
|
||||
library_on_tick();
|
||||
int ready = 1;
|
||||
int rows = library_test_rows();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
meow_list_item it;
|
||||
if (!library_test_src(i, &it)) continue;
|
||||
if (it.subtitle && strcmp(it.subtitle, "…") == 0) { ready = 0; break; }
|
||||
}
|
||||
if (ready) return;
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
static int row_index_for(const char *name)
|
||||
{
|
||||
int rows = library_test_rows();
|
||||
@@ -147,10 +133,25 @@ static int row_index_for(const char *name)
|
||||
static void tap_row(const char *name)
|
||||
{
|
||||
int idx = row_index_for(name);
|
||||
if (idx < 0) { CHECK(0, "row %s not found", name); return; }
|
||||
int list_top = meow_ui_statusbar_height() + 56;
|
||||
library_on_tap(120, list_top + idx * 56 + 28);
|
||||
}
|
||||
|
||||
/* 某张图片行在本目录图片自然序里的下标 = 排在前面的「图片 · 」行数。 */
|
||||
static int expected_image_index(const char *name)
|
||||
{
|
||||
int ti = row_index_for(name);
|
||||
if (ti < 0) return -1;
|
||||
int n = 0;
|
||||
for (int i = 0; i < ti; i++) {
|
||||
meow_list_item it;
|
||||
if (!library_test_src(i, &it)) continue;
|
||||
if (it.subtitle && strncmp(it.subtitle, "图片 · ", strlen("图片 · ")) == 0) n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const char *base = getenv("TMPDIR");
|
||||
@@ -161,25 +162,27 @@ int main(void)
|
||||
if (!data) { printf("SKIP: mkdtemp failed\n"); return 0; }
|
||||
setenv("MEOW_DATA_DIR", data, 1);
|
||||
|
||||
char root[MR_PATH_MAX], parent[MR_PATH_MAX], sub[MR_PATH_MAX], p[MR_PATH_MAX];
|
||||
char root[MR_PATH_MAX], pics[MR_PATH_MAX], other[MR_PATH_MAX], p[MR_PATH_MAX];
|
||||
snprintf(root, sizeof root, "%s/library", data);
|
||||
snprintf(parent, sizeof parent, "%s/Pictures", root);
|
||||
if (mkdir_p(parent) != 0) { printf("SKIP: mkdir failed\n"); return 0; }
|
||||
snprintf(pics, sizeof pics, "%s/Pictures", root);
|
||||
snprintf(other, sizeof other, "%s/Other", root);
|
||||
if (mkdir_p(pics) != 0 || mkdir_p(other) != 0) { printf("SKIP: mkdir failed\n"); return 0; }
|
||||
|
||||
/* 父目录:一张封面图 + 两个含图子目录(用户报告的结构)。 */
|
||||
snprintf(p, sizeof p, "%s/cover.jpg", parent); /* 扩展名即可;打开只枚举不解码 */
|
||||
FILE *f = fopen(p, "wb");
|
||||
if (!f) { printf("SKIP: cover write failed\n"); return 0; }
|
||||
fputs("x", f);
|
||||
fclose(f);
|
||||
snprintf(sub, sizeof sub, "%s/2023", parent);
|
||||
mkdir_p(sub);
|
||||
snprintf(p, sizeof p, "%s/a.bmp", sub);
|
||||
write_bmp24(p, 40, 60);
|
||||
snprintf(sub, sizeof sub, "%s/2024", parent);
|
||||
mkdir_p(sub);
|
||||
snprintf(p, sizeof p, "%s/b.bmp", sub);
|
||||
write_bmp24(p, 40, 60);
|
||||
/* Pictures:6 张图(数字序号/大小写/前导零混排)+ 1 个普通文件(不成行)。 */
|
||||
const char *imgs[] = { "A.bmp", "a.bmp", "img2.bmp", "img10.bmp",
|
||||
"page3.bmp", "Page03.bmp" };
|
||||
for (unsigned i = 0; i < sizeof imgs / sizeof imgs[0]; i++) {
|
||||
snprintf(p, sizeof p, "%s/%s", pics, imgs[i]);
|
||||
if (write_bmp24(p, 40, 60) != 0) { printf("SKIP: image write failed\n"); return 0; }
|
||||
}
|
||||
snprintf(p, sizeof p, "%s/notes.txt", pics);
|
||||
{ FILE *f = fopen(p, "wb"); if (f) { fputs("x", f); fclose(f); } }
|
||||
|
||||
/* Other:4 张图,用于「已在本页时换册重进」。 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
snprintf(p, sizeof p, "%s/o%d.bmp", other, i + 1);
|
||||
if (write_bmp24(p, 40, 60) != 0) { printf("SKIP: image write failed\n"); return 0; }
|
||||
}
|
||||
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
if (meow_ui_init(&o) != 0) { printf("SKIP: meow_ui_init failed (no host backend)\n"); return 0; }
|
||||
@@ -190,46 +193,52 @@ int main(void)
|
||||
meow_view_page_push(ACT_LIBRARY);
|
||||
|
||||
CHECK(library_test_load(root) == 1, "load fixture root");
|
||||
wait_ready(3000);
|
||||
|
||||
int ip = row_index_for("Pictures");
|
||||
meow_list_item it;
|
||||
CHECK(ip >= 0 && library_test_src(ip, &it), "Pictures row present");
|
||||
CHECK(it.subtitle && strcmp(it.subtitle, "1 page") == 0,
|
||||
"Pictures probed as book with direct cover (subtitle=%s)",
|
||||
it.subtitle ? it.subtitle : "");
|
||||
|
||||
/* 点击父目录 → 进阅读器;cover 是唯一直接子图 → count=1,不能是 open_err。 */
|
||||
/* 点目录 = 进入目录,不打开阅读器。 */
|
||||
tap_row("Pictures");
|
||||
CHECK(meow_ui_activity_active() == ACT_READER, "tap opens reader");
|
||||
CHECK(strcmp(mr_reader_dir(), parent) == 0, "reader dir=%s", mr_reader_dir());
|
||||
CHECK(mr_reader_ui_open_err() == 0, "parent folder opens without open_err");
|
||||
CHECK(mr_reader_ui_count() == 1, "parent book pages=%d (want 1)",
|
||||
mr_reader_ui_count());
|
||||
CHECK(library_test_worker_on() == 0,
|
||||
"library background decode stopped while reader is active");
|
||||
for (int i = 0; i < 30; i++) { usleep(10 * 1000); }
|
||||
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "tap dir stays in library");
|
||||
CHECK(strcmp(library_test_dir(), pics) == 0, "entered Pictures (%s)", library_test_dir());
|
||||
CHECK(library_test_rows() == 6, "Pictures rows=%d (6 images, txt excluded)",
|
||||
library_test_rows());
|
||||
|
||||
/* 点 img10.bmp:阅读器从该图开始(count=6, page=它在该目录的自然序下标)。 */
|
||||
int want = expected_image_index("img10.bmp");
|
||||
CHECK(want == 3, "img10.bmp natural image index=%d (want 3)", want);
|
||||
tap_row("img10.bmp");
|
||||
CHECK(meow_ui_activity_active() == ACT_READER, "tap image opens reader");
|
||||
CHECK(strcmp(mr_reader_dir(), pics) == 0, "reader dir=%s", mr_reader_dir());
|
||||
CHECK(mr_reader_ui_open_err() == 0, "open has no error");
|
||||
CHECK(mr_reader_ui_count() == 6, "reader pages=%d (want 6)", mr_reader_ui_count());
|
||||
CHECK(mr_reader_ui_page() == want, "reader starts at selected image: page=%d (want %d)",
|
||||
mr_reader_ui_page(), want);
|
||||
|
||||
/* 让首帧解码 tick 跑一遍,确认不会因坏夹具进入 open_err。 */
|
||||
for (int i = 0; i < 20; i++) usleep(10 * 1000);
|
||||
CHECK(mr_reader_ui_open_err() == 0, "still no open_err after decode tick");
|
||||
|
||||
/* 返回书库 → 后台线程恢复,再次点开(同目录二次打开 + 续读恢复)。 */
|
||||
/* 已在本页:直接换册重进(M17 遗留修复,SDK 会忽略重复 push)。 */
|
||||
mr_reader_open_dir_at(other, 2);
|
||||
CHECK(meow_ui_activity_active() == ACT_READER, "switch keeps reader active");
|
||||
CHECK(strcmp(mr_reader_dir(), other) == 0, "switched reader dir=%s", mr_reader_dir());
|
||||
CHECK(mr_reader_ui_count() == 4, "switched book pages=%d (want 4)", mr_reader_ui_count());
|
||||
CHECK(mr_reader_ui_page() == 2, "switched book page=%d (want 2)", mr_reader_ui_page());
|
||||
|
||||
/* 返回书库 → 再点另一张图(同目录二次打开 + 显式起始页优先于续读)。 */
|
||||
meow_view_page_pop();
|
||||
CHECK(meow_ui_activity_active() == ACT_LIBRARY, "back to library");
|
||||
CHECK(library_test_worker_on() == 1, "library background decode restarted on return");
|
||||
tap_row("Pictures");
|
||||
CHECK(mr_reader_ui_open_err() == 0, "reopen parent without open_err");
|
||||
CHECK(mr_reader_ui_count() == 1, "reopen pages=%d", mr_reader_ui_count());
|
||||
CHECK(strcmp(library_test_dir(), pics) == 0, "library still in Pictures");
|
||||
|
||||
tap_row("page3.bmp");
|
||||
CHECK(meow_ui_activity_active() == ACT_READER, "reopen reader");
|
||||
CHECK(mr_reader_ui_open_err() == 0, "reopen no error");
|
||||
CHECK(mr_reader_ui_count() == 6, "reopen pages=%d", mr_reader_ui_count());
|
||||
int want2 = expected_image_index("page3.bmp");
|
||||
CHECK(want2 == 4, "page3.bmp natural image index=%d (want 4)", want2);
|
||||
CHECK(mr_reader_ui_page() == want2, "reopen starts at page=%d (want %d)",
|
||||
mr_reader_ui_page(), want2);
|
||||
meow_view_page_pop();
|
||||
|
||||
/* 封面是大图也不影响打开(打开只枚举文件名)。 */
|
||||
snprintf(p, sizeof p, "%s/cover.bmp", parent);
|
||||
CHECK(write_bmp24(p, 400, 600) == 0, "write large cover");
|
||||
CHECK(library_test_load(root) == 1, "reload after cover change");
|
||||
wait_ready(3000);
|
||||
tap_row("Pictures");
|
||||
CHECK(mr_reader_ui_open_err() == 0, "open with large cover has no open_err");
|
||||
CHECK(mr_reader_ui_count() == 2, "cover.jpg + cover.bmp pages=%d (want 2)",
|
||||
mr_reader_ui_count());
|
||||
meow_view_page_pop();
|
||||
/* 档案册路径(无 zip 夹具时只验证入口不被当成目录):点 txt 不存在行;跳过。 */
|
||||
|
||||
meow_view_free(reader);
|
||||
meow_view_free(lib);
|
||||
|
||||
Reference in new issue
Block a user