feat(reader): M27 书架按目录分组(ui.shelf_group=folder|format,默认 format 不变)
- 新增 shelf_model.c/.h:扫描/行序列从 shelf.c 拆出(纯 C,宿主可测);shelf_book 加 group[96](相对 Books/ 的目录路径,根下空串),path 语义/内容完全不变(进度键仍为 rdprog_hash(path)) - folder 模式:目录段头(根下=未分类),未分类置顶、其余按目录名排序;组内沿用最近阅读优先/其次书名;空目录不产生段头 - format 模式逐行保持旧行为;新增 reader.conf ui.shelf_group,缺键默认 format - 上限 256→2048(SHELF_MAX_ROWS=2N=4096,段头≤书数),超限置可读提示,不再静默截断 - 顶栏加「目录/格式」切换,设置页加「书架分组」;UTF-8 安全截断长中文目录名 - 单测 test/shelf_group_smoke.c(38 断言)+ 计时 test/shelf_scan_bench.c - build_reader.sh:链 meow_ui.a 解析冒烟 stbi_*;无 qemu 时跳过执行而非失败
This commit is contained in:
1 parent
db672cbe8f
commit
409fd1edf7
11 files changed
+800
-129
No files matched your search
@@ -0,0 +1,73 @@
|
||||
/* app/shelf_model.h — 书架数据模型:扫描 Books/ + 生成行序列(纯 C,不依赖 SDK/设备)。
|
||||
*
|
||||
* 从 shelf.c 拆出,供 shelf.c(控件装配)、宿主单测(test/shelf_group_smoke.c)与
|
||||
* 扫描计时(test/shelf_scan_bench.c)共用。
|
||||
*
|
||||
* 目录归属(M27):folder 模式按「相对 Books/ 的目录路径」分组,如 "轻小说"、"科幻/长篇";
|
||||
* Books/ 根下的书 group="",显示段头 SHELF_GROUP_ROOT。分组键是相对路径(不是目录名),
|
||||
* 故 Books/a/b 与 Books/b 不会混。
|
||||
*
|
||||
* 硬约束:path 永远是书的完整路径(打开书 + 进度键 rdprog_hash(path)),分组只写 group,
|
||||
* 绝不改写 path;两个分组模式间切换不影响进度。
|
||||
*/
|
||||
#ifndef SHELF_MODEL_H
|
||||
#define SHELF_MODEL_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* 书籍上限:真实场景 180 本(Books/轻小说),2048 给 11× 余量。
|
||||
* 超限不再静默停扫:report->truncated=1,由 UI 出提示。 */
|
||||
#define SHELF_MAX_BOOKS 2048
|
||||
#define SHELF_GROUP_MAX 96 /* 目录字段容量(含 NUL):95 字节 ≈ 31 个汉字 */
|
||||
/* 行数上限:每本书最多贡献 1 行书 + 1 行段头(段头数 ≤ 书数),故 2N 必够。 */
|
||||
#define SHELF_MAX_ROWS (SHELF_MAX_BOOKS * 2)
|
||||
#define SHELF_GROUP_ROOT "未分类" /* Books/ 根下书籍的段头 */
|
||||
|
||||
typedef struct shelf_book {
|
||||
char path[400]; /* 完整路径:打开书 + 进度键;分组不得改写 */
|
||||
char title[128];
|
||||
char sub[160];
|
||||
char group[SHELF_GROUP_MAX]; /* 相对 Books/ 的目录路径;根下="" */
|
||||
int fmt; /* 0 txt,1 fb2,2 epub,3 mobi,4 cbz */
|
||||
long size;
|
||||
uint64_t ts; /* 最近阅读 unix;无=0 */
|
||||
} shelf_book;
|
||||
|
||||
typedef struct shelf_row {
|
||||
int section; /* 1=段头行 */
|
||||
int fmt; /* 段头/书行格式;段头无 label 时显示格式名 */
|
||||
int book; /* 书行=books 下标;段头=-1 */
|
||||
const char *label; /* 段头标题;NULL=格式名(指向 books[].group 或静态串) */
|
||||
} shelf_row;
|
||||
|
||||
/* 进度时间戳查询(生产=rdprog_load;测试/基线=NULL,全部 ts=0)。 */
|
||||
typedef uint64_t (*shelf_ts_fn)(const char *path);
|
||||
|
||||
typedef struct shelf_scan_report {
|
||||
int nbooks;
|
||||
int truncated; /* 1=有书因 SHELF_MAX_BOOKS 上限被丢弃 */
|
||||
int root_missing; /* 1=root 打不开(无卡/无 Books 目录) */
|
||||
} shelf_scan_report;
|
||||
|
||||
/* 递归扫描 root(子目录深度 ≤2,与旧行为一致),按 ts 降序/书名升序排序后写 books(容量 cap)。
|
||||
* 返回写入本数。目录为空不产生任何记录(段头由 build_rows 按书生成,天然无空段头)。 */
|
||||
int shelf_scan_books(const char *root, shelf_book *books, int cap,
|
||||
shelf_ts_fn ts_fn, shelf_scan_report *rep);
|
||||
|
||||
/* 生成行序列:
|
||||
* folder_mode=0:按格式分段(TXT/FB2/EPUB/MOBI/CBZ,空格式不出段头)——与 M27 前逐行一致;
|
||||
* folder_mode=1:先按目录分组,根目录组「未分类」置顶,其余组按目录名排序;
|
||||
* 组内沿用 books[] 的全局顺序(最近阅读优先、其次书名)。
|
||||
* 返回行数;超过 rowcap 时只写 rowcap 行并返回 rowcap。 */
|
||||
int shelf_build_rows(const shelf_book *books, int nbooks, int folder_mode,
|
||||
shelf_row *rows, int rowcap);
|
||||
|
||||
/* 工具(单测直接调用)。 */
|
||||
int shelf_fmt_from_ext(const char *name);
|
||||
const char *shelf_fmt_name(int fmt);
|
||||
void shelf_fmt_size(char *out, int outsz, long size);
|
||||
/* UTF-8 安全截断:最多写 cap-1 字节;若截断点落在多字节序列中间,整段丢弃该残缺字符。
|
||||
* 返回写入字节数(不含 NUL)。 */
|
||||
int shelf_copy_utf8(char *dst, int cap, const char *src);
|
||||
|
||||
#endif /* SHELF_MODEL_H */
|
||||
Reference in new issue
Block a user