图库: 书库点图改按文件名定位(修 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:
1 parent
64336f95d9
commit
4befbfe14c
17 files changed
+523
-112
No files matched your search
+32
-9
@@ -257,13 +257,16 @@ int mr_library_scan(mr_library *lib, const char *dir, int max_depth)
|
||||
|
||||
int mr_library_row_at(const mr_library *lib, int idx, mr_library_row *out)
|
||||
{
|
||||
static char trunc_sub[48];
|
||||
static char trunc_sub[96];
|
||||
static int trunc_ready;
|
||||
|
||||
if (!lib || !out) return 0;
|
||||
if (idx == lib->count && lib->truncated) {
|
||||
if (!trunc_ready) {
|
||||
snprintf(trunc_sub, sizeof trunc_sub, "仅显示前 %d 项", MR_LIB_SCAN_MAX);
|
||||
/* 截断只是列表显示上限:图片仍能从任一可见图片进入阅读器后翻到,
|
||||
* 文案不要让人误以为「没有更多图片」。 */
|
||||
snprintf(trunc_sub, sizeof trunc_sub,
|
||||
"仅显示前 %d 项(图片可在阅读器中翻到)", MR_LIB_SCAN_MAX);
|
||||
trunc_ready = 1;
|
||||
}
|
||||
memset(out, 0, sizeof *out);
|
||||
@@ -284,14 +287,15 @@ int mr_library_row_at(const mr_library *lib, int idx, mr_library_row *out)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int mr_library_image_index(const mr_library *lib, int idx)
|
||||
int mr_path_within_root(const char *path, const char *root)
|
||||
{
|
||||
if (!lib || idx < 0 || idx >= lib->count) return -1;
|
||||
if (lib->items[idx].kind != MR_LIB_KIND_IMAGE) return -1;
|
||||
int n = 0;
|
||||
for (int i = 0; i < idx; i++)
|
||||
if (lib->items[i].kind == MR_LIB_KIND_IMAGE) n++;
|
||||
return n;
|
||||
if (!path || !path[0] || !root || !root[0]) return 0;
|
||||
size_t rl = strlen(root);
|
||||
while (rl > 1 && root[rl - 1] == '/') rl--;
|
||||
if (rl == 1 && root[0] == '/') return path[0] == '/';
|
||||
if (strncmp(path, root, rl) != 0) return 0;
|
||||
char c = path[rl];
|
||||
return c == '\0' || c == '/';
|
||||
}
|
||||
|
||||
int mr_path_parent(const char *path, char *out, int outsz)
|
||||
@@ -433,6 +437,25 @@ int mr_book_adjacent_ex(const char *dir, int delta, char *out, int outsz,
|
||||
return rc;
|
||||
}
|
||||
|
||||
int mr_book_adjacent_rooted(const char *dir, int delta, const char *root,
|
||||
mr_lib_archive_fn archive_pages, void *ud,
|
||||
char *out, int outsz)
|
||||
{
|
||||
if (!out || outsz <= 0) return -1;
|
||||
out[0] = '\0';
|
||||
if (!dir || !dir[0] || !root || !root[0]) return -1;
|
||||
/* 只允许在浏览根内翻册:当前册或相邻册越根一律当「没有相邻册」。 */
|
||||
if (!mr_path_within_root(dir, root)) return 0;
|
||||
|
||||
int r = mr_book_adjacent_ex(dir, delta, out, outsz, archive_pages, ud);
|
||||
if (r != 1) return r;
|
||||
if (!mr_path_within_root(out, root)) {
|
||||
out[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int mr_book_adjacent(const char *dir, int delta, char *out, int outsz)
|
||||
{
|
||||
return mr_book_adjacent_ex(dir, delta, out, outsz, NULL, NULL);
|
||||
|
||||
+11
-6
@@ -86,12 +86,9 @@ int mr_path_parent(const char *path, char *out, int outsz);
|
||||
* 返回的 title/subtitle 指向 lib 内部或函数内静态缓冲(截断提示行),调用方不要长期持有。 */
|
||||
int mr_library_row_at(const mr_library *lib, int idx, mr_library_row *out);
|
||||
|
||||
/* 图片行在该目录图片自然序中的下标(0 基);idx 不是图片行/越界返回 -1。
|
||||
*
|
||||
* ⚠️ 这是书库→阅读器的契约:阅读器的目录册(logic/reader/page_source.c)只枚举
|
||||
* 「非隐藏 + mr_path_is_image + 普通文件」并按 mr_natural_cmp 排序。书库扫描用的是
|
||||
* 同一套过滤器与比较器,因此本函数数出的下标就是 mr_reader_open_dir_at() 的起始页。 */
|
||||
int mr_library_image_index(const mr_library *lib, int idx);
|
||||
/* path 是否落在浏览根内(等于 root 或以 "root/" 开头)。1=在内;0=越界/参数非法。
|
||||
* 根锁定用:书库导航与阅读器「相邻册」都不得越过 SD 卡根。 */
|
||||
int mr_path_within_root(const char *path, const char *root);
|
||||
|
||||
/* 目录是否**直接**含受支持图片。1=有;0=没有/不可读。
|
||||
* 仅用于阅读器的「相邻册」判定,不再是书库行语义。 */
|
||||
@@ -111,6 +108,14 @@ typedef int (*mr_lib_archive_fn)(const char *path, void *ud);
|
||||
int mr_book_adjacent_ex(const char *dir, int delta, char *out, int outsz,
|
||||
mr_lib_archive_fn archive_pages, void *ud);
|
||||
|
||||
/* 册间衔接(根锁定版):在 mr_book_adjacent_ex 之上拒绝浏览根之外的相邻册。
|
||||
* dir 必须在 root 内;找到的相邻册也必须在 root 内,否则按「没有相邻册」返回 0。
|
||||
* 1=找到并写入 out;0=没有相邻册或越根被拒(out 为空串);-1=参数非法。
|
||||
* 阅读器用它保证「读到末页翻册」不会跳到 SD 卡根之外(例如挂载点以上的其它目录)。 */
|
||||
int mr_book_adjacent_rooted(const char *dir, int delta, const char *root,
|
||||
mr_lib_archive_fn archive_pages, void *ud,
|
||||
char *out, int outsz);
|
||||
|
||||
#ifdef MR_LIBRARY_TEST_HOOKS
|
||||
/* 宿主测试用 IO 计数(scan/dir_has_images 的 opendir/readdir/lstat 次数;线程安全不保证)。 */
|
||||
typedef struct mr_library_io_stats {
|
||||
|
||||
@@ -323,7 +323,7 @@ int mr_cache_store(const char *path, const void *data, size_t len)
|
||||
int i = idx_find_locked(path);
|
||||
if (i >= 0) idx_remove_locked(i);
|
||||
make_room_locked((long long)len);
|
||||
pthread_mutex_unlock(&g_lock); /* 写盘不持锁:大页不阻塞缩略图线程 */
|
||||
pthread_mutex_unlock(&g_lock); /* 写盘不持锁:大页不阻塞并发读者 */
|
||||
|
||||
FILE *f = fopen(tmp, "wb");
|
||||
int ok = f && fwrite(data, 1, len, f) == len;
|
||||
|
||||
@@ -5,21 +5,20 @@
|
||||
* 解码回调按路径解码——两种阅读模式(翻页/条漫)与目录册走完全相同的代码路径。
|
||||
*
|
||||
* 目录与上限(纪律):
|
||||
* - 目录 = <数据目录>/cache(数据目录形如 .../MeowData/os.meow.picbrowser/data,
|
||||
* 与缩略图缓存同目录;文件名统一前缀 mrpage_,与缩略图 *.bmp 互不影响);
|
||||
* - 目录 = <数据目录>/cache(数据目录形如 .../MeowData/os.meow.picbrowser/data);
|
||||
* 文件名统一前缀 mrpage_,与其它可能落在这目录的临时文件互不影响;
|
||||
* - 上限 = 64 页 或 32MiB,先到者触发 LRU 淘汰(按最近使用计数);
|
||||
* - **单条目溢出**:某页解压后本身就超过 32MiB 时允许一次性溢出——只保留该条、
|
||||
* 其它条目全部淘汰,占用上限受页源单页上限 64MiB(MR_ZIP_PAGE_MAX_BYTES)约束。
|
||||
* 这样超大页仍可阅读,不会因为预算拒绝落缓存而变成永久空白页;
|
||||
* - mr_cache_init() 首次成功时清掉上一进程遗留的 mrpage_*(孤儿页)
|
||||
* (注意:缩略图 LRU 也必须在同一目录里跳过 mrpage_*,否则会互删);
|
||||
* - mr_cache_init() 首次成功时清掉上一进程遗留的 mrpage_*(孤儿页);
|
||||
* - 写入走 "<name>.part" + rename,进程崩溃不会留下半个缓存文件。
|
||||
*
|
||||
* 键 = 档案路径 + 档案 mtime/size + 条目名 + 页下标:档案被替换(mtime/size 变)或
|
||||
* 同名条目重复出现时不会命中旧内容。文件名保留图片扩展名(解码入口按扩展名区分
|
||||
* JPEG 的「解码期缩解」路径)。
|
||||
*
|
||||
* 线程:内部互斥量;可在解码线程/缩略图线程/主线程并发调用。
|
||||
* 线程:内部互斥量;可在解码线程/主线程并发调用。
|
||||
* 纯 C:宿主测试可直接编译(见 tuku/test/test_cbz.c)。
|
||||
*/
|
||||
#ifndef MR_PAGE_CACHE_H
|
||||
@@ -42,8 +41,8 @@ extern "C" {
|
||||
* 成功 0;参数非法/缓冲不足 -1。 */
|
||||
int mr_cache_dir_from_data(const char *data_dir, char *out, int outsz);
|
||||
|
||||
/* 文件名是否属于解压页缓存(mrpage_ 前缀)。缩略图 LRU 与启动清孤儿共用这一判据,
|
||||
* 保证两套缓存同目录时互不误删(P2-1)。1=解压页缓存。 */
|
||||
/* 文件名是否属于解压页缓存(mrpage_ 前缀)。启动清孤儿用这一判据,
|
||||
* 保证同目录的其它文件不被误删。1=解压页缓存。 */
|
||||
int mr_cache_is_page_name(const char *name);
|
||||
|
||||
/* 建立/切换缓存目录并清孤儿。0=可用;-1=不可用(之后 store/touch 均失败)。
|
||||
|
||||
Reference in new issue
Block a user