Files
MeowOS-SDK/tuku/logic/library.h
T
AskaEth 9f34417c49 图库: 单层浏览 + 懒判定与 M7/M11 整合;探测缓存 P2 修复(M13)
rebase 到 picbrowser(含 M7 条漫/续读、M11 CBZ 档案册)后的语义拼接:
- 保留单层扫描重写:一次 opendir/readdir+lstat 只列当前层(目录 + 注入 archive_pages
  时的 .cbz/.zip 档案文件),自然升序、隐藏项/符号链接跳过、2000 条截断标注;
  系统目录/空目录/无图目录一律列出;彻底删除递归「子树含图」探测
- 保留并复用 M11 档案册分支:add_dir_entry / add_archive_entry / entry_subtitle /
  entry_badge / archive_pages_at / sibling_is_book / mr_library_scan_ex 注入;
  档案册行同样先成行、页数由后台 probe(注入 zip 页数回调)懒算
- 保留 M7:mr_library_dir_has_images / mr_book_adjacent(_ex) 语义不变;为 adjacency
  恢复 ignored_dir_name(仅册间衔接跳过系统/隐藏目录,浏览本身不隐藏);
  mr_library_format_subtitle 在补位后拼「· 续读 Pn」角标
- mr_library_probe_path 统一探测目录/档案册:mtime(秒+纳秒)+size 失效的 256 项 LRU +
  互斥锁;评审 P2:只缓存「探测前后 stat 完全一致」的结果,期间变化则不写缓存
  (消除「不完整结果绑定新 mtime」的假新鲜;下次调用重新探测)
- UI:列表用 src2 每帧取行;后台线程探测优先于缩略图;档案册封面解压档内首图(M11);
  行语义=书页数/缩略图、空目录「空目录」、无图/坏档「无图片」;可上溯至文件系统 /、
  不可读目录提示并停留;TopAppBar「TF卡」回根;启动时根目录不可读也建空列表;
  顶栏回根命中加 g_tb_home_idx>=0 防御
- tuku_internal.h:library_go_up 注释校正(0=到达文件系统 /,app.c 据此退出)
- 测试:test_library 改单层语义;新增 browse_nav / browse_lazy / browse_perf /
  browse_ui_host(含评审 P2 的「探测期间变化不写缓存」用例);test_cont_adjacent A6
  改两段式;test_cbz T8/T10 改两段式(坏档/无图档案列出但非书);
  run_tests.sh 保留双方全部条目(browse 4 套 + run_zip_tests/run_cbz_tests)
- 复验:run_tests.sh 全 PASS(含 M11 zip/cbz)、run_webtoon_tests.sh 全 PASS;
  worktree 内 sdk/build_sdk.sh + build_tuku.sh 零警告静态 MIPS32r2 ELF
2026-11-05 12:23:31 +08:00

190 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* logic/library.h — 书库目录浏览(纯 C,不依赖 meow_ui.h)。
*
* 分层契约同 examples/hello:logic 层只含文件系统与纯数据,宿主 gcc 可直接裸测。
*
* M13 浏览语义(用户要求:性能 + 访问放开;参考 os.meow.files 的 fsx_scan_dir):
* - mr_library_scan / mr_library_scan_ex 只枚举**当前目录一层**:一次 opendir/readdir +
* 逐项 lstat,列出全部子目录(自然升序),不做任何递归「子树含图」探测;
* - 访问放开:系统目录(MeowAPPs/MeowData/MeowOS/lost+found)不再隐藏,
* 空目录/无图目录一律列出并可进入;仅跳过 "."/".." 与其它 "." 开头的隐藏项、
* 符号链接(防环);
* - 「直接含图目录=一本书」的判定改为**后台懒算**:mr_library_probe_dir 只读一层,
* mr_library_probe_path 按行类型(目录/档案册)走同一套 mtime 失效缓存(线程安全),
* 结果经 mr_library_apply_probe 补回行(页数/首图/副标题)。首帧/滚动不做子目录 IO;
* - 单目录条目上限 MR_LIB_SCAN_MAX(默认 2000):到上限后若还有条目则截断并置
* lib->truncated=1(UI 用 mr_library_row_at 末尾的提示行标注)。
*
* M11 档案册(.cbz/.zip):注入 archive_pages 回调时,当前层的档案文件也成行(与目录同列
* 自然序混排);档案册的页数同样走懒探测(mr_library_probe_path + 注入回调),logic 层
* 不依赖 zip 容器。未注入时(archive_pages==NULL)只列目录,等价 M7 书库。
*
* M7 续读角标 / 册间衔接:mr_library_format_subtitle 在探测完成后拼「· 续读 Pn」
* (mr_cont_badge);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
/* 行懒判定状态:0=待探测(副标题占位「…」)/1=已探测/2=探测失败(不可读)。 */
#define MR_LIB_PENDING 0
#define MR_LIB_READY 1
#define MR_LIB_FAILED 2
typedef struct mr_library_entry {
char name[MR_NAME_MAX]; /* 目录名/档案文件名(行主标题) */
char path[MR_PATH_MAX]; /* 完整路径(导航/进 reader/探测用) */
char cover[MR_PATH_MAX]; /* 后台探测填:自然序第一张直接子图(档案册留空,UI 解压首图) */
char subtitle[48]; /* 占位「…」;探测后 "N pages"/"空目录"/"无图片"/"无法读取" */
int pages; /* -1=未判定;>=0=直接图片数(档案册=档内图片页数) */
int subdirs; /* 兼容字段(M11 及旧读者):新扫描不统计,恒 0 */
int has_images; /* 1=书;0=非书;-1=未判定 */
int probe_state; /* MR_LIB_PENDING / MR_LIB_READY / MR_LIB_FAILED */
int empty; /* 1=空目录(目录且 probe_state==READY 后有效) */
int is_archive; /* 1=.cbz/.zip 档案册行 */
} mr_library_entry;
typedef struct mr_library {
mr_library_entry *items;
int count; /* 当前层列出的条目数 */
int cap;
int truncated; /* 1=达到 MR_LIB_SCAN_MAX 被截断 */
} mr_library;
/* 单行探测结果(目录=本层直接图片数;档案册=档内图片页数;均不递归)。 */
typedef struct mr_library_probe {
int pages; /* 受支持图片数(png/jpg/jpeg/bmp;档案册=档内页数) */
int empty; /* 1=目录没有可见子项(档案册恒 0) */
char cover[MR_PATH_MAX]; /* 目录的自然序第一张图(档案册留空) */
} mr_library_probe;
/* 行数据(纯内存合成;UI 的每帧 src 回调直接取用,零 IO)。 */
typedef struct mr_library_row {
const char *title; /* 主标题 */
const char *subtitle; /* 副标题(可空串) */
int is_book; /* 1=书(显示页数/缩略图,点入阅读,无进入箭头) */
int is_archive; /* 1=档案册行 */
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(先释放 lib 旧内容)。
* 返回条目数(>=0);dir 不可读/不存在/不是目录返回 -1(lib 仍为空且可安全 free)。
* max_depth 仅为兼容旧调用保留,已忽略(不再递归)。 */
int mr_library_scan(mr_library *lib, const char *dir, int max_depth);
/* ============================================================ */
/* 档案册(M11):.cbz/.zip 当一本书,与文件夹册同列 */
/* ------------------------------------------------------------
* logic 层不依赖 zip 容器(保持宿主裸测/链接面干净):档案册探测由 UI 注入。
* archive_pages(path, ud) 返回档内图片页数(>0=是一本书),0=不是/坏档。
* 传 NULL 时行为与 M7 完全一致(纯目录书库,档案文件不成行)。
* 档案册行:name=文件名、path=档案路径、is_archive=1、cover 留空(UI 解压首图)、
* 页数/书标记由后台 probe 经同一注入回调懒算。
* ============================================================ */
typedef int (*mr_lib_archive_fn)(const char *path, void *ud);
int mr_library_scan_ex(mr_library *lib, const char *dir, int max_depth,
mr_lib_archive_fn archive_pages, void *ud);
/* 释放条目数组并清零(可重复调用)。 */
void mr_library_free(mr_library *lib);
/* 取父目录路径(去掉末尾 '/' 后截断最后一段)。根 "/" 返回 "/"。
* 成功 0;参数非法/输出缓冲不足 -1。可一路向上到文件系统 '/'。 */
int mr_path_parent(const char *path, char *out, int outsz);
/* ---- 后台懒判定(单层探测;可由工作线程调用) ---- */
/* 单层探测目录 dir:直接图片数 + 自然序首图 + 是否空目录。不递归、不跟随符号链接。
* 0=成功;-1=参数非法/不可读(out 清零)。 */
int mr_library_probe_dir(const char *dir, mr_library_probe *out);
/* 带缓存探测目录:按目录 mtime(秒+纳秒)+ size 失效;内部互斥锁,可多线程并发调用。
* 0=成功(可能来自缓存);-1=不可读(失败不缓存)。 */
int mr_library_probe_cached(const char *dir, mr_library_probe *out);
/* 按行探测(目录或档案册),带同一套缓存:
* is_archive=0 → 目录(等价 mr_library_probe_cached);
* is_archive=1 → 调 archive_pages(path,ud) 取档内页数(cover 留空、empty=0);
* archive_pages 为 NULL 时返回 -1。
* 0=成功;-1=参数非法/不可读。探测期间 mtime/size 变化则不写入缓存(避免假新鲜)。 */
int mr_library_probe_path(const char *path, int is_archive,
mr_lib_archive_fn archive_pages, void *ud,
mr_library_probe *out);
/* 清空探测缓存(目录整体切换/测试用)。 */
void mr_library_probe_cache_clear(void);
/* 把探测结果补回第 idx 行(pages/has_images/cover/subtitle/empty)。
* 1=已应用;0=参数非法/越界。 */
int mr_library_apply_probe(mr_library *lib, int idx, const mr_library_probe *p);
/* 标记第 idx 行探测失败(不可读):副标题「无法读取」,视为非书。1=已应用;0=非法。 */
int mr_library_mark_error(mr_library *lib, int idx);
/* 按行当前状态重算副标题(占位/页数/空目录/无图片/无法读取),
* 并在书行(has_images==1)后缀「· 续读 Pn」(M7 角标)。 */
void mr_library_format_subtitle(mr_library_entry *e);
/* 行合成(纯内存、零 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);
/* 目录是否**直接**含受支持图片(= 书库语义里的「一本书」,可直接进 reader)。
* 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 完全等价。 */
int mr_book_adjacent_ex(const char *dir, int delta, char *out, int outsz,
mr_lib_archive_fn archive_pages, void *ud);
#ifdef MR_LIBRARY_TEST_HOOKS
/* 宿主测试用 IO 计数(scan/probe_dir 的 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 */