图库: 书库改文件管理器模型 + 关闭缩略图 + 根锁定/无卡容错(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,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;
|
||||
|
||||
Reference in new issue
Block a user