图库: 书库改文件管理器模型 + 关闭缩略图 + 根锁定/无卡容错(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:
AskaEth committed 2026-11-05 20:26:40 +08:00
1 parent d07b99056f
commit 64336f95d9
16 files changed
+940 -1927

No files matched your search

+67 -43
View File
@@ -1,17 +1,16 @@
/* test_browse_perf.c — 单层扫描性能/有界性验收(纯 C,无 SDK)。
/* test_browse_perf.c — 单层扫描 / 行合成性能与有界性验收(纯 C,无 SDK)。
*
* 夹具:500 个顶层目录 × 8 层嵌套(4000 个子目录)+ 根目录 2000 个文件。
* 断言(对应 M13 任务⑤):
* P1 首帧扫描只做单层:opendir 次数 == 1(旧实现会递归打开数千个目录);
* lstat 次数 == 本层条目数;耗时有界;
* P2 后台探测同样只读一层:每个目录一次 opendir,不进入其嵌套子树;
* P3 行合成(UI src/bind 的数据面)纯内存、零 IO、O(1);
* P4 深目录直接扫描也只打开它自己。
* 夹具:500 个顶层目录 × 8 层嵌套(4000 个子目录)+ 根目录 2000 个普通文件
* + 一个 big/ 目录(2000 张图片,验证 2000 项大目录)。
* 断言(对应 M18 任务③):
* P1 首帧扫描只做单层:opendir 次数 == 1,lstat 次数 == 本层条目数,耗时有界;
* P2 2000 项大目录:一次扫描出全部 2000 行,opendir=1、lstat=2000,耗时有界;
* P3 行合成(UI src 的数据面)纯内存、零 IO、耗时有界(2000 行 × 1000 遍);
* P4 进入深层/普通子目录同样只打开它自己(进入目录成本 = 该目录一层)。
*
* 编译(在 tuku/ 下):
* gcc -Wall -Wextra -O1 -I. -I../sdk -DMR_LIBRARY_TEST_HOOKS \
* test/test_browse_perf.c logic/library.c logic/reader.c test/settings_reader_stub.c \
* -pthread -o build/host/test_browse_perf
* test/test_browse_perf.c logic/library.c -pthread -o build/host/test_browse_perf
*/
#include "logic/library.h"
@@ -26,6 +25,7 @@
#define PERF_DIRS 500
#define PERF_DEPTH 8
#define PERF_FILES 2000
#define PERF_BIG 2000
static int g_pass, g_fail;
#define CHECK(cond, ...) do { \
@@ -92,7 +92,7 @@ int main(void)
if (!root) return 1;
char p[MR_PATH_MAX];
/* 根目录:2000 个文件 + 500 个顶层目录(每个顶层目录内是 8 层嵌套 + 1 张图)。 */
/* 根目录:2000 个普通文件(不成行)+ 500 个顶层目录(每个顶层目录内是 8 层嵌套 + 1 张图)。 */
for (int f = 0; f < PERF_FILES; f++) {
snprintf(p, sizeof p, "%s/f%04d.txt", root, f);
if (touch_file(p) != 0) { CHECK(0, "file %s", p); break; }
@@ -101,7 +101,7 @@ int main(void)
char deep[MR_PATH_MAX];
snprintf(deep, sizeof deep, "%s/d%04d", root, d);
if (mkdir_p(deep) != 0) { CHECK(0, "mkdir %s", deep); break; }
snprintf(p, sizeof p, "%s/001.png", deep); /* 顶层目录自身直接含图(=一本书) */
snprintf(p, sizeof p, "%s/001.png", deep);
if (touch_file(p) != 0) { CHECK(0, "touched top image"); break; }
for (int l = 1; l <= PERF_DEPTH; l++) {
char sub[MR_PATH_MAX];
@@ -113,6 +113,15 @@ int main(void)
if (touch_file(p) != 0) { CHECK(0, "touched deep image"); break; }
}
/* 2000 项大目录(图片)。 */
char big[MR_PATH_MAX];
snprintf(big, sizeof big, "%s/big", root);
CHECK(mkdir_p(big) == 0, "mkdir big");
for (int i = 0; i < PERF_BIG; i++) {
snprintf(p, sizeof p, "%s/img%04d.png", big, i);
if (touch_file(p) != 0) { CHECK(0, "big image %s", p); break; }
}
/* ---- P1:首帧扫描只做单层 ---- */
mr_library lib;
memset(&lib, 0, sizeof lib);
@@ -123,61 +132,67 @@ int main(void)
mr_library_io_stats st;
mr_library_test_io_get(&st);
CHECK(n == PERF_DIRS, "scan lists %d dirs (got %d)", PERF_DIRS, n);
CHECK(n == PERF_DIRS + 1, "scan lists %d dirs (got %d)", PERF_DIRS + 1, n);
CHECK(lib.truncated == 0, "not truncated");
CHECK(st.opendirs == 1, "single-layer scan: opendir=%ld (want 1)", st.opendirs);
CHECK(st.lstats == PERF_FILES + PERF_DIRS,
"lstat only current level: %ld (want %d)", st.lstats, PERF_FILES + PERF_DIRS);
CHECK(st.lstats == PERF_FILES + PERF_DIRS + 1,
"lstat only current level: %ld (want %d)", st.lstats, PERF_FILES + PERF_DIRS + 1);
CHECK(scan_us < 1500000LL, "scan bounded: %lldus", scan_us);
if (lib.count > 1) {
CHECK(strcmp(lib.items[0].name, "d0000") == 0, "first=%s", lib.items[0].name);
if (lib.count == PERF_DIRS + 1) {
CHECK(strcmp(lib.items[0].name, "big") == 0, "first=%s", lib.items[0].name);
CHECK(strcmp(lib.items[lib.count - 1].name, "d0499") == 0,
"last=%s", lib.items[lib.count - 1].name);
}
printf(" scan: %lldus, %d dirs, opendir=%ld lstat=%ld\n",
printf(" root scan: %lldus, %d rows, opendir=%ld lstat=%ld\n",
scan_us, n, st.opendirs, st.lstats);
mr_library_free(&lib);
/* ---- P2:后台探测每目录只开一次,不进入嵌套子树 ---- */
/* ---- P2:2000 项大目录(进入目录 = 单层扫描) ---- */
mr_library biglib;
memset(&biglib, 0, sizeof biglib);
mr_library_test_io_reset();
long long p0 = now_us();
int bad = 0;
for (int i = 0; i < lib.count; i++) {
mr_library_probe probe;
if (mr_library_probe_cached(lib.items[i].path, &probe) != 0 || probe.pages != 1)
bad++;
}
long long probe_us = now_us() - p0;
long long b0 = now_us();
int bn = mr_library_scan(&biglib, big, MR_LIB_MAX_DEPTH);
long long big_us = now_us() - b0;
mr_library_test_io_get(&st);
CHECK(bad == 0, "all top dirs have 1 direct image (bad=%d)", bad);
CHECK(st.opendirs == PERF_DIRS,
"probe stays shallow: opendir=%ld (want %d, not %d)",
st.opendirs, PERF_DIRS, PERF_DIRS * (PERF_DEPTH + 1));
CHECK(st.lstats == PERF_DIRS,
"probe lstats only image candidates: %ld (want %d)", st.lstats, PERF_DIRS);
CHECK(probe_us < 5000000LL, "probe bounded: %lldus", probe_us);
printf(" probe: %lldus, opendir=%ld lstat=%ld\n", probe_us, st.opendirs, st.lstats);
CHECK(bn == PERF_BIG, "big dir rows=%d (want %d)", bn, PERF_BIG);
CHECK(biglib.truncated == 0, "big dir not truncated at cap");
CHECK(st.opendirs == 1, "big dir scan: opendir=%ld", st.opendirs);
CHECK(st.lstats == PERF_BIG, "big dir scan: lstat=%ld (want %d)", st.lstats, PERF_BIG);
CHECK(big_us < 1500000LL, "big dir scan bounded: %lldus", big_us);
if (biglib.count == PERF_BIG) {
CHECK(strcmp(biglib.items[0].name, "img0000.png") == 0, "first=%s", biglib.items[0].name);
CHECK(strcmp(biglib.items[PERF_BIG - 1].name, "img1999.png") == 0,
"last=%s", biglib.items[PERF_BIG - 1].name);
CHECK(biglib.items[PERF_BIG - 1].kind == MR_LIB_KIND_IMAGE, "last is image");
CHECK(mr_library_image_index(&biglib, PERF_BIG - 1) == PERF_BIG - 1,
"image index at end");
}
printf(" big dir scan: %lldus, %d rows, opendir=%ld lstat=%ld\n",
big_us, bn, st.opendirs, st.lstats);
/* ---- P3:行合成(bind/src 数据面)纯内存 O(1) ---- */
/* ---- P3:行合成(UI src 数据面)纯内存、2000 行 × 1000 遍 ---- */
mr_library_test_io_reset();
long long r0 = now_us();
int rows = 0;
for (int r = 0; r < 1000; r++)
for (int i = 0; i < lib.count; i++) {
for (int i = 0; i < biglib.count; i++) {
mr_library_row row;
if (mr_library_row_at(&lib, i, &row)) rows++;
if (mr_library_row_at(&biglib, i, &row)) rows++;
}
long long row_us = now_us() - r0;
mr_library_test_io_get(&st);
CHECK(rows == 1000 * lib.count, "rows synthesized (%d)", rows);
CHECK(rows == 1000 * biglib.count, "rows synthesized (%d)", rows);
CHECK(st.opendirs == 0 && st.readdirs == 0 && st.lstats == 0,
"row synthesis does no IO (open=%ld rd=%ld stat=%ld)",
st.opendirs, st.readdirs, st.lstats);
CHECK(row_us < 1000000LL, "row synthesis bounded: %lldus", row_us);
printf(" rows: %lldus for %d rows, zero IO\n", row_us, rows);
mr_library_free(&biglib);
/* ---- P4:直接扫描深层目录同样只打开它自己 ---- */
/* ---- P4:进入子目录只打开它自己(不递归子树) ---- */
char deep[MR_PATH_MAX];
snprintf(deep, sizeof deep, "%s", lib.items[0].path);
snprintf(deep, sizeof deep, "%s/d0000", root);
for (int l = 1; l <= PERF_DEPTH; l++) {
char sub[MR_PATH_MAX];
snprintf(sub, sizeof sub, "%s/l%d", deep, l);
@@ -186,13 +201,22 @@ int main(void)
mr_library_test_io_reset();
mr_library deep_lib;
memset(&deep_lib, 0, sizeof deep_lib);
CHECK(mr_library_scan(&deep_lib, deep, MR_LIB_MAX_DEPTH) == 0, "deep leaf has no subdir");
CHECK(mr_library_scan(&deep_lib, deep, MR_LIB_MAX_DEPTH) == 1, "deep leaf has one image");
mr_library_test_io_get(&st);
CHECK(st.opendirs == 1, "deep scan opens once (got %ld)", st.opendirs);
CHECK(st.lstats == 1, "deep scan lstats only its image (got %ld)", st.lstats);
mr_library_free(&deep_lib);
mr_library_free(&lib);
/* 顶层目录:一个子目录 + 一张图 = 2 行,只打开它自己。 */
snprintf(deep, sizeof deep, "%s/d0000", root);
memset(&deep_lib, 0, sizeof deep_lib);
mr_library_test_io_reset();
CHECK(mr_library_scan(&deep_lib, deep, MR_LIB_MAX_DEPTH) == 2, "top dir rows");
mr_library_test_io_get(&st);
CHECK(st.opendirs == 1 && st.lstats == 2, "top dir scan: open=%ld stat=%ld",
st.opendirs, st.lstats);
mr_library_free(&deep_lib);
rm_rf(root);
printf("%s: %d passed, %d failed\n", g_fail ? "FAIL" : "PASS", g_pass, g_fail);
return g_fail ? 1 : 0;