图库: 书库点图改按文件名定位(修 P1 截断错位)+ 册间衔接根锁定(M18 评审 r1)

P1-1(会静默开错图):取消「书库从列表推导图片下标」。
- 新增 mr_reader_open_dir_at_name(dir, name):阅读器打开目录册后,在**自己刚枚举出的
  全量目录册**(mr_dir_book,与翻页同一份枚举)里按 basename 定位起始页(先精确,
  再大小写不敏感兜底;找不到退回续读/首页)。书库点击只传文件名。
- 删除 mr_library_image_index(不再有跨枚举的隐式契约)。
- 截断提示文案改为「仅显示前 2000 项(图片可在阅读器中翻到)」。
- 修复前(cb02722 探针,2100 张 4 位名夹具):rows=2000/truncated=1/
  library_images=2000/reader_pages=2100/**mismatched=590**(首行 p1412 → 打开 p1411);
  评审夹具为 1995/2000。修复后:2000 个可见图片行按名定位 **0 mismatch**,
  2100 页全量可翻;26 次真实 UI 点开(首尾各 3 + 20 个确定性伪随机)逐张命中。

连带处理(评审 r1):
1. 删掉 reader_page_enter 的 c->view 死写(reopen 判定用 activity active/depth);
   顺手删掉 library_page_create 里同款死写。
2. 阅读器册间衔接根锁定:新增 mr_path_within_root + mr_book_adjacent_rooted,
   book_switch 用浏览根(默认 MR_ROOT_DIR,测试可注入)钳制相邻册;
   根外的兄弟册按「没有相邻册」处理。test_cont_adjacent A7 验证「明文 _ex 会越根、
   rooted 拒绝、根内翻册不受影响」;test_mode_switch_host H6 验证条漫末页不越根。
3. test_reader_perf 不再依赖顶栏 tap 命中时序:模式切换改走测试钩子
   mr_reader_test_toggle_mode()(与顶栏动作同一个 mode_set),负载下稳定。
4. page_cache 注释去掉「缩略图缓存/缩略图线程」过时描述(只改文字)。

回归:run_tests.sh(library 81 / nav 74 / lazy 57 / perf 25 / … / open_book 含 2100 用例)
与 run_webtoon_tests.sh(含 A7/H6)全 PASS;交叉构建零警告。
This commit is contained in:
AskaEth committed 2026-11-05 20:26:40 +08:00
1 parent 64336f95d9
commit 4befbfe14c
17 files changed
+523 -112

No files matched your search

+79 -3
View File
@@ -7,7 +7,9 @@
* A4 排除:隐藏/系统目录、符号链接、普通文件不参与兄弟册;
* A5 参数:delta 非 ±1、NULL/小缓冲、不存在的目录;
* A6 书库行模型(M18):目录+图片+档案册同列、目录优先;副标题扫描期生成,
* 不再有「· 续读 Pn」角标(角标随「目录自动当书」链路删除,续读仍由 reader 恢复)。
* 不再有「· 续读 Pn」角标(角标随「目录自动当书」链路删除,续读仍由 reader 恢复);
* A7 册间衔接根锁定(M18 评审 P2-1):相邻册越过浏览根时按「没有相邻册」处理,
* 根内正常翻册不受影响(mr_book_adjacent_rooted)。
*
* 编译(在 tuku/ 下):
* gcc -Wall -Wextra -O1 -I. test/test_cont_adjacent.c logic/library.c logic/reader.c \
@@ -160,7 +162,7 @@ static void test_adjacent(const char *root)
}
/* A6 文件管理器行模型:扫描即出最终行(无占位/无后台补位/无续读角标);
* 目录优先;当前层的图片行带图片下标(点图 → reader 起始页)。 */
* 目录优先;图片/档案册按类型成行。 */
static void test_library_rows(const char *root)
{
mr_test_cfg_clear();
@@ -191,7 +193,6 @@ static void test_library_rows(const char *root)
if (strcmp(e->name, "loose.png") == 0) {
seen_img = 1;
CHECK(e->kind == MR_LIB_KIND_IMAGE, "loose.png is an image row");
CHECK(mr_library_image_index(&lib, i) == 0, "loose.png is image #0");
}
}
CHECK(seen1 == 1, "vol1 listed");
@@ -199,6 +200,80 @@ static void test_library_rows(const char *root)
mr_library_free(&lib);
}
/* A7 册间衔接的根锁定:相邻册必须在浏览根内,否则按「没有相邻册」处理。
* 夹具:base/{aaa_outside, root}/{...};root 自己也是「册」(直接含图),
* 明文 _ex 会给出 root 的兄弟 aaa_outside,rooted 必须拒绝(不越 SD 卡根)。 */
static void test_rooted_adjacent(void)
{
char tmpl[] = "./mr_rooted_XXXXXX";
char *base = mkdtemp(tmpl);
CHECK(base != NULL, "mkdtemp rooted fixture");
if (!base) return;
char root[MR_PATH_MAX], outside[MR_PATH_MAX], in1[MR_PATH_MAX], in2[MR_PATH_MAX];
joinp(root, sizeof root, base, "root");
joinp(outside, sizeof outside, base, "aaa_outside");
joinp(in1, sizeof in1, root, "aaa_inside1");
joinp(in2, sizeof in2, root, "aaa_inside2");
CHECK(mkdir_p(outside) == 0 && mkdir_p(in1) == 0 && mkdir_p(in2) == 0,
"mkdir rooted fixture");
char p[MR_PATH_MAX];
joinp(p, sizeof p, root, "000.png"); CHECK(touch_file(p) == 0, "root image");
joinp(p, sizeof p, outside, "001.png"); CHECK(touch_file(p) == 0, "outside image");
joinp(p, sizeof p, in1, "001.png"); CHECK(touch_file(p) == 0, "inside1 image");
joinp(p, sizeof p, in2, "001.png"); CHECK(touch_file(p) == 0, "inside2 image");
char out[MR_PATH_MAX];
/* 明文衔接:root 的兄弟 aaa_outside 在浏览根之外,_ex 会返回它。 */
memset(out, 0, sizeof out);
CHECK(mr_book_adjacent_ex(root, -1, out, (int)sizeof out, NULL, NULL) == 1 &&
strcmp(out, outside) == 0,
"plain adjacency reaches outside root (%s)", out);
/* 根锁定版:同样的调用必须拒绝越根。 */
memset(out, 0x7f, sizeof out);
out[0] = '\0';
CHECK(mr_book_adjacent_rooted(root, -1, root, NULL, NULL,
out, (int)sizeof out) == 0 && out[0] == '\0',
"rooted adjacency refuses book outside root (%s)", out);
CHECK(mr_book_adjacent_rooted(root, 1, root, NULL, NULL,
out, (int)sizeof out) == 0 && out[0] == '\0',
"rooted adjacency: no next inside root");
/* 根内正常翻册仍然可用(不能被误锁死)。 */
memset(out, 0, sizeof out);
CHECK(mr_book_adjacent_rooted(in1, 1, root, NULL, NULL,
out, (int)sizeof out) == 1 &&
strcmp(out, in2) == 0, "rooted next inside root (%s)", out);
CHECK(mr_book_adjacent_rooted(in2, -1, root, NULL, NULL,
out, (int)sizeof out) == 1 &&
strcmp(out, in1) == 0, "rooted prev inside root (%s)", out);
CHECK(mr_book_adjacent_rooted(in1, -1, root, NULL, NULL,
out, (int)sizeof out) == 0,
"rooted: no book before inside1 (outside is out of root)");
CHECK(mr_book_adjacent_rooted(in2, 1, root, NULL, NULL,
out, (int)sizeof out) == 0,
"rooted: no book after inside2");
/* 当前册本身在根外 → 视为没有相邻册。 */
CHECK(mr_book_adjacent_rooted(outside, 1, root, NULL, NULL,
out, (int)sizeof out) == 0, "current dir outside root");
/* 参数边界。 */
CHECK(mr_book_adjacent_rooted(root, 1, NULL, NULL, NULL,
out, (int)sizeof out) == -1, "rooted NULL root");
CHECK(mr_book_adjacent_rooted(NULL, 1, root, NULL, NULL,
out, (int)sizeof out) == -1, "rooted NULL dir");
CHECK(mr_book_adjacent_rooted(root, 1, root, NULL, NULL, NULL, 32) == -1,
"rooted NULL out");
CHECK(mr_book_adjacent_rooted(root, 1, "", NULL, NULL,
out, (int)sizeof out) == -1, "rooted empty root");
rm_rf(base);
}
int main(void)
{
char tmpl[] = "./mr_cont_adj_XXXXXX";
@@ -240,6 +315,7 @@ int main(void)
test_has_images(root);
test_adjacent(root);
test_library_rows(root);
test_rooted_adjacent();
rm_rf(root);
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);