字体:find_font_rec/resolve_font 从 reader.c 抽到 app/font_resolve.c 并加进程内记忆化。 只缓存成功路径;命中后仍做一次 access() 校验(字库被删即重扫);没找到不缓存, 用户之后拷入的字库下次开书即可找到。顶栏「刷新」额外调 font_cache_invalidate(), 给「新拷入字库、旧字库仍在」一条显式失效路径。宿主实测:35 目录树 walk 0.026~0.54ms (=300+ 次系统调用)vs 命中 0.001ms(1 次 access);开书路径每次省一次完整 walk。 徽标:副题格式化抽成 shelf_fmt_sub()(扫描与「刚读完置顶」共用一份实现), promote_just_read() 改 ts 后立即重算这本的 sub/sub_folder,「· 读」不再等下次全量刷新。 回归:test_reader_core 6846、shelf_group_smoke 63(+8)、font_resolve_smoke 14 全绿; build_reader.sh 0 warning;扫描缓存/进度语义未改。app.json 0.1.3/code4 → 0.1.4/code5。
77 lines
3.1 KiB
C
77 lines
3.1 KiB
C
/* app/font_resolve.c — 递归(深度 ≤ 3)扫 MeowOS/fonts 找首个 .otf/.ttf,进程内记忆化。
|
|
*
|
|
* 为什么缓存:fonts 树在 SD 卡(FUSE)上,每次开书都要调用;一次完整 walk 是
|
|
* 「35+ 个目录的 opendir/readdir + 每个条目的 stat」,真机实测单次 10~250ms 抖动。
|
|
*
|
|
* 失效策略(红线:不许出现「明明有字库却说没有」):
|
|
* 1) 只缓存成功路径。没找到 → 返回 NULL 且不写缓存,用户之后拷入字库,下次开书即可找到。
|
|
* 2) 命中后仍做一次 access(路径, R_OK) 校验(1 次 stat):
|
|
* 失败(被删/改名/不可读)→ 丢缓存重新 walk,立刻回到真实答案;
|
|
* 成功 → 直接用。
|
|
* 1 次 stat 比 35+ 个目录的 walk 便宜一到两个数量级(见 test/font_resolve_bench.c),
|
|
* 所以正确性和省时两头都要。
|
|
* 3) 命中与未命中的结果集与加缓存前一致:缓存前每次 walk 取的也是「readdir 顺序第一个」
|
|
* 字库(同目录内容下顺序稳定),新增字库不会改变既有命中。
|
|
* 4) 显式失效:font_cache_invalidate()(书架顶栏「刷新」调用)——用户新拷入字库且旧字库仍在时,
|
|
* 缓存命中不会自动改选,刷新给一条确定的失效路径。
|
|
*/
|
|
#include "font_resolve.h"
|
|
|
|
#include <dirent.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#define FONT_PATH_MAX 400
|
|
#define FONT_SCAN_DEPTH 3
|
|
|
|
static char g_cached[FONT_PATH_MAX]; /* "" = 无有效缓存(从未找到,或已失效) */
|
|
|
|
/* 命中即写 out;0=没找到。 */
|
|
static int find_font_rec(const char *dir, int depth, char *out, int outsz)
|
|
{
|
|
if (depth > FONT_SCAN_DEPTH) return 0;
|
|
DIR *dp = opendir(dir);
|
|
if (!dp) return 0;
|
|
struct dirent *de;
|
|
int found = 0;
|
|
while ((de = readdir(dp)) && !found) {
|
|
if (de->d_name[0] == '.') continue;
|
|
char full[FONT_PATH_MAX + 80];
|
|
if (snprintf(full, sizeof full, "%s/%s", dir, de->d_name) >= (int)sizeof full) continue;
|
|
struct stat st;
|
|
if (stat(full, &st) != 0) continue;
|
|
if (S_ISDIR(st.st_mode)) { found = find_font_rec(full, depth + 1, out, outsz); }
|
|
else {
|
|
const char *d = strrchr(de->d_name, '.');
|
|
if (d && (strcasecmp(d, ".otf") == 0 || strcasecmp(d, ".ttf") == 0)) {
|
|
if (strlen(full) < (size_t)outsz) {
|
|
strcpy(out, full);
|
|
found = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
closedir(dp);
|
|
return found;
|
|
}
|
|
|
|
const char *font_resolve(const char *root)
|
|
{
|
|
if (g_cached[0]) {
|
|
if (access(g_cached, R_OK) == 0) return g_cached; /* 命中:1 次 stat 校验 */
|
|
g_cached[0] = 0; /* 字库没了:缓存失效,重扫 */
|
|
}
|
|
if (!root || !root[0]) return NULL;
|
|
if (find_font_rec(root, 0, g_cached, (int)sizeof g_cached)) return g_cached;
|
|
g_cached[0] = 0; /* 没找到:不缓存(之后拷入的字库下次仍能找到) */
|
|
return NULL;
|
|
}
|
|
|
|
void font_cache_invalidate(void)
|
|
{
|
|
g_cached[0] = 0;
|
|
}
|