- 新增 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 时跳过执行而非失败
124 lines
4.6 KiB
C
124 lines
4.6 KiB
C
/* app/reader_conf.c — 阅读器 App 私有配置实现(见 app/reader_conf.h)。 */
|
||
#include "reader_conf.h"
|
||
#include "meow_ui.h" /* meow_config_dir() */
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/types.h>
|
||
|
||
/* 从 MEOW_DATA_DIR(<TF>/MeowData/<pkg>/data)剥掉尾 "/data",得到 <TF>/MeowData/<pkg>。 */
|
||
static void strip_data_suffix(const char *d, char *out, int outsz)
|
||
{
|
||
if (!d || !d[0]) { strncpy(out, "/tmp", (size_t)outsz - 1); out[outsz - 1] = 0; return; }
|
||
size_t n = strlen(d);
|
||
const char *suffix = "/data";
|
||
size_t sl = strlen(suffix);
|
||
size_t keep;
|
||
if (n >= sl && strcmp(d + n - sl, suffix) == 0) keep = n - sl;
|
||
else keep = n;
|
||
if (keep >= (size_t)outsz) keep = (size_t)outsz - 1;
|
||
memcpy(out, d, keep);
|
||
out[keep] = 0;
|
||
}
|
||
|
||
static void conf_path(char *out, int outsz)
|
||
{
|
||
const char *data = meow_config_dir();
|
||
char base[360];
|
||
strip_data_suffix(data ? data : "", base, sizeof base);
|
||
if (snprintf(out, (size_t)outsz, "%s/reader.conf", base) >= outsz) out[0] = 0;
|
||
/* mkdir -p base */
|
||
char tmp[400];
|
||
strncpy(tmp, base, sizeof tmp - 1); tmp[sizeof tmp - 1] = 0;
|
||
for (char *p = tmp + 1; *p; p++) {
|
||
if (*p == '/') { *p = 0; mkdir(tmp, 0755); *p = '/'; }
|
||
}
|
||
mkdir(tmp, 0755);
|
||
}
|
||
|
||
static void conf_defaults(rdconf *c)
|
||
{
|
||
c->fs = 20;
|
||
c->ls = 130;
|
||
strcpy(c->bg, "black"); /* B 定案:默认纯黑 */
|
||
strcpy(c->theme, "auto");
|
||
strcpy(c->shelf, "list"); /* A 定案:默认 list */
|
||
strcpy(c->shelf_group, "format");/* M27:默认按格式,升级不改变现有用户书架 */
|
||
c->header = 1;
|
||
c->footer = 1;
|
||
c->font[0] = 0;
|
||
}
|
||
|
||
static void clamp_str(char *v, size_t sz)
|
||
{
|
||
v[sz - 1] = 0;
|
||
}
|
||
|
||
rdconf *rdconf_get(void)
|
||
{
|
||
static rdconf g;
|
||
static int loaded = 0;
|
||
if (loaded) return &g;
|
||
conf_defaults(&g);
|
||
char path[440];
|
||
conf_path(path, sizeof path);
|
||
FILE *f = path[0] ? fopen(path, "r") : NULL;
|
||
if (f) {
|
||
char line[512];
|
||
while (fgets(line, sizeof line, f)) {
|
||
line[strcspn(line, "\r\n")] = 0;
|
||
char *eq = strchr(line, '=');
|
||
if (!eq) continue;
|
||
*eq = 0;
|
||
const char *k = line, *v = eq + 1;
|
||
if (!strcmp(k, "ui.shelf_layout")) { strncpy(g.shelf, v, sizeof g.shelf - 1); g.shelf[sizeof g.shelf - 1] = 0; }
|
||
else if (!strcmp(k, "ui.shelf_group")) { strncpy(g.shelf_group, v, sizeof g.shelf_group - 1); g.shelf_group[sizeof g.shelf_group - 1] = 0; }
|
||
else if (!strcmp(k, "rd.fs")) { int t = atoi(v); if (t >= 14 && t <= 40) g.fs = t; }
|
||
else if (!strcmp(k, "rd.ls")) { int t = atoi(v); if (t >= 100 && t <= 200) g.ls = t; }
|
||
else if (!strcmp(k, "rd.bg")) { strncpy(g.bg, v, sizeof g.bg - 1); g.bg[sizeof g.bg - 1] = 0; }
|
||
else if (!strcmp(k, "rd.theme")) { strncpy(g.theme, v, sizeof g.theme - 1); g.theme[sizeof g.theme - 1] = 0; }
|
||
else if (!strcmp(k, "rd.header")) { g.header = atoi(v) ? 1 : 0; }
|
||
else if (!strcmp(k, "rd.footer")) { g.footer = atoi(v) ? 1 : 0; }
|
||
else if (!strcmp(k, "rd.font")) { strncpy(g.font, v, sizeof g.font - 1); g.font[sizeof g.font - 1] = 0; }
|
||
}
|
||
fclose(f);
|
||
}
|
||
/* 校验合法取值(越界回默认/回退),避免脏档破坏 UI */
|
||
if (strcmp(g.shelf, "grid") && strcmp(g.shelf, "list")) strcpy(g.shelf, "list");
|
||
if (strcmp(g.shelf_group, "folder") && strcmp(g.shelf_group, "format"))
|
||
strcpy(g.shelf_group, "format");
|
||
if (strcmp(g.bg, "black") && strcmp(g.bg, "white") &&
|
||
strcmp(g.bg, "sepia") && strcmp(g.bg, "auto")) strcpy(g.bg, "black");
|
||
if (strcmp(g.theme, "light") && strcmp(g.theme, "dark") &&
|
||
strcmp(g.theme, "auto")) strcpy(g.theme, "auto");
|
||
clamp_str(g.shelf, sizeof g.shelf);
|
||
clamp_str(g.shelf_group, sizeof g.shelf_group);
|
||
clamp_str(g.bg, sizeof g.bg);
|
||
clamp_str(g.theme, sizeof g.theme);
|
||
clamp_str(g.font, sizeof g.font);
|
||
loaded = 1;
|
||
return &g;
|
||
}
|
||
|
||
int rdconf_persist(void)
|
||
{
|
||
rdconf *c = rdconf_get();
|
||
char path[440];
|
||
conf_path(path, sizeof path);
|
||
if (!path[0]) return -1;
|
||
FILE *f = fopen(path, "wb");
|
||
if (!f) return -5;
|
||
fprintf(f, "ui.shelf_layout=%s\n", c->shelf);
|
||
fprintf(f, "ui.shelf_group=%s\n", c->shelf_group);
|
||
fprintf(f, "rd.fs=%d\n", c->fs);
|
||
fprintf(f, "rd.ls=%d\n", c->ls);
|
||
fprintf(f, "rd.bg=%s\n", c->bg);
|
||
fprintf(f, "rd.theme=%s\n", c->theme);
|
||
fprintf(f, "rd.header=%d\n", c->header);
|
||
fprintf(f, "rd.footer=%d\n", c->footer);
|
||
fprintf(f, "rd.font=%s\n", c->font);
|
||
fclose(f);
|
||
return 0;
|
||
}
|