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;交叉构建零警告。
135 lines
7.1 KiB
C
135 lines
7.1 KiB
C
/* logic/library.h — 书库文件管理器模型(纯 C,不依赖 meow_ui.h)。
|
|
*
|
|
* 分层契约同 examples/hello:logic 层只含文件系统与纯数据,宿主 gcc 可直接裸测。
|
|
*
|
|
* M18 浏览语义(用户要求「完全像文件管理器」):
|
|
* - 列表 = 当前目录一层的「子目录 + 受支持图片(png/jpg/jpeg/bmp) + 档案册(.cbz/.zip)」;
|
|
* 目录优先,组内自然序;其它普通文件不列;隐藏项("." 开头)与符号链接跳过;
|
|
* - **没有**「目录含图=一本书」的判定:不探测、不算页数、不取封面、不拼续读角标;
|
|
* 点目录就是进目录(空目录也进),点图片从该图开始读,点档案册打开档案册;
|
|
* - 扫描只做一次 opendir/readdir + 逐项 lstat;副标题(类型 · 大小)在扫描时生成一次,
|
|
* 渲染期零 IO、零字符串格式化(UI row 回调直接取指针);
|
|
* - 单目录条目上限 MR_LIB_SCAN_MAX:到上限后若还有条目则截断并置 lib->truncated=1。
|
|
*
|
|
* 仍保留的阅读器辅助:mr_library_dir_has_images / mr_book_adjacent(_ex)
|
|
* (阅读到册末时翻到同父目录的相邻册,与书库列表模型无关)。
|
|
*/
|
|
#ifndef MR_LIBRARY_H
|
|
#define MR_LIBRARY_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#define MR_PATH_MAX 512
|
|
#define MR_NAME_MAX 256
|
|
#define MR_LIB_MAX_DEPTH 8 /* 兼容旧调用:扫描不递归,该参数被忽略 */
|
|
|
|
/* 单目录列出的条目上限(超大目录截断 + UI 提示;可用 -D 覆写便于宿主测试)。 */
|
|
#ifndef MR_LIB_SCAN_MAX
|
|
#define MR_LIB_SCAN_MAX 2000
|
|
#endif
|
|
|
|
/* 行类型(列表图标/点击行为由 UI 按此分派)。 */
|
|
#define MR_LIB_KIND_DIR 0 /* 子目录:点=进入 */
|
|
#define MR_LIB_KIND_IMAGE 1 /* 受支持图片:点=从该图开始阅读 */
|
|
#define MR_LIB_KIND_ARCHIVE 2 /* .cbz/.zip 档案册:点=打开档案册 */
|
|
|
|
typedef struct mr_library_entry {
|
|
char name[MR_NAME_MAX]; /* 文件名/目录名(行主标题) */
|
|
char path[MR_PATH_MAX]; /* 完整路径 */
|
|
char subtitle[64]; /* 扫描时预生成一次:「文件夹」/「图片 · 1.2 MB」/「档案册 · 1.2 MB」 */
|
|
long long size; /* 普通文件字节数;目录=0 */
|
|
int kind; /* MR_LIB_KIND_* */
|
|
} mr_library_entry;
|
|
|
|
typedef struct mr_library {
|
|
mr_library_entry *items;
|
|
int count; /* 当前层列出的条目数 */
|
|
int cap;
|
|
int truncated; /* 1=达到 MR_LIB_SCAN_MAX 被截断 */
|
|
} mr_library;
|
|
|
|
/* 行数据(纯内存合成;UI 的每帧 src 回调直接取用,零 IO、零格式化)。 */
|
|
typedef struct mr_library_row {
|
|
const char *title; /* 主标题 */
|
|
const char *subtitle; /* 副标题(预生成,永不为 NULL) */
|
|
int kind; /* MR_LIB_KIND_* */
|
|
int disabled; /* 1=提示行(不可点,如截断标注) */
|
|
int index; /* 对应 mr_library.items 下标;提示行=-1 */
|
|
} mr_library_row;
|
|
|
|
/* 扩展名判断:png/jpg/jpeg/bmp(大小写不敏感)。1=受支持图片。 */
|
|
int mr_path_is_image(const char *name);
|
|
|
|
/* 档案册扩展名判断:cbz / zip(大小写不敏感)。1=档案册。 */
|
|
int mr_path_is_archive(const char *name);
|
|
|
|
/* 自然排序比较:数字段按数值比(page2 < page10),数字相等时前导零少的在前;
|
|
* 非数字段 ASCII 大小写不敏感,完全等价时按原始字节序定序(结果确定,无抖动)。 */
|
|
int mr_natural_cmp(const char *a, const char *b);
|
|
|
|
/* 扫描 dir 的**直接子项**(单层):子目录 + 受支持图片 + 档案册,写入 lib(先释放旧内容)。
|
|
* 返回条目数(>=0);dir 不可读/不存在/不是目录返回 -1(lib 仍为空且可安全 free)。
|
|
* max_depth 仅为兼容旧调用保留,已忽略(不再递归)。 */
|
|
int mr_library_scan(mr_library *lib, const char *dir, int max_depth);
|
|
|
|
/* 释放条目数组并清零(可重复调用)。 */
|
|
void mr_library_free(mr_library *lib);
|
|
|
|
/* 取父目录路径(去掉末尾 '/' 后截断最后一段)。根 "/" 返回 "/"。
|
|
* 成功 0;参数非法/输出缓冲不足 -1。纯路径工具,不做根目录限制(限制在 UI 导航层)。 */
|
|
int mr_path_parent(const char *path, char *out, int outsz);
|
|
|
|
/* 行合成(纯内存、零 IO、O(1);UI 线程每帧调用):idx 越界返回 0。
|
|
* idx==count 且 lib->truncated 时输出末尾提示行(disabled=1)。
|
|
* 返回的 title/subtitle 指向 lib 内部或函数内静态缓冲(截断提示行),调用方不要长期持有。 */
|
|
int mr_library_row_at(const mr_library *lib, int idx, mr_library_row *out);
|
|
|
|
/* path 是否落在浏览根内(等于 root 或以 "root/" 开头)。1=在内;0=越界/参数非法。
|
|
* 根锁定用:书库导航与阅读器「相邻册」都不得越过 SD 卡根。 */
|
|
int mr_path_within_root(const char *path, const char *root);
|
|
|
|
/* 目录是否**直接**含受支持图片。1=有;0=没有/不可读。
|
|
* 仅用于阅读器的「相邻册」判定,不再是书库行语义。 */
|
|
int mr_library_dir_has_images(const char *dir);
|
|
|
|
/* 册间衔接(M7):同父目录下按自然序排列的「直接含图」兄弟册。
|
|
* delta=+1 下一册 / -1 上一册。1=找到并写入 out 完整路径;
|
|
* 0=没有相邻册(当前册是首/末册,或当前册不在兄弟表中);-1=参数非法。
|
|
* 语义:跳过隐藏/系统目录与符号链接,排序同 mr_natural_cmp。
|
|
* 只算文件夹册(直接含图目录)——M7 口径不变。 */
|
|
int mr_book_adjacent(const char *dir, int delta, char *out, int outsz);
|
|
|
|
/* 册间衔接 + 档案册(M11):兄弟表 = 「直接含图目录」∪「含图片页的 .cbz/.zip」,
|
|
* 两者同列按名称自然序混排(跨册衔接按同父目录自然序)。
|
|
* archive_pages 传 NULL 时与 mr_book_adjacent 完全等价。 */
|
|
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 {
|
|
long opendirs;
|
|
long readdirs;
|
|
long lstats;
|
|
} mr_library_io_stats;
|
|
|
|
void mr_library_test_io_reset(void);
|
|
void mr_library_test_io_get(mr_library_io_stats *out);
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
#endif /* MR_LIBRARY_H */
|