feat: M6.43 — reader分段流式读取(Phase1,只txt) + 面板前台管理/返回回桌面按钮
- reader分段(只txt): core/seg.{h,c} 512KB码位对齐切段(尾字完整)+manifest+按需读单段; book.c rd_book_open只布局每页所在段(治CPU100%/返回无效) load_seg只读当前段(非txt原路径); reader.c 进度实时保存(翻页/刮擦/字号/编码dirty+on_tick节流落盘+退出即时写,锚点存段号,崩溃不丢进度); rdtxt.c fread替代fgetc(去开书扫描卡顿); Phase2(双段拼接+4%释放)留待
- 面板: /api/ams/bg加fg/fg_count(后台保持向后兼容)+POST /api/ams/:pkg/killfg(前台救命:优先am kill,launcher拒则pid直信号SIGTERM->SIGKILL绕过门控)+前端/#/bg前台组'终止'绿徽标+确认弹窗(一键清仍只后台不误杀前台); POST /api/input/gesture(返回=左缘右滑/回桌面=底部上滑,注入滑动轨迹复用SDK手势)+前端屏幕控制页加返回/回桌面MD3按钮
0warning;宿主test_reader_core 72-0/seg_smoke 19-0/loaders_smoke/book_smoke/formats_smoke PASS;面板selftest/node check PASS
This commit is contained in:
1 parent
a64ae06eb6
commit
f40abbd494
17 files changed
+1069
-70
No files matched your search
@@ -74,15 +74,32 @@ int rdtxt_reader_next(rdtxt_reader *r, uint8_t *utf8, size_t cap,
|
||||
long start = ftell(r->f);
|
||||
if (start < 0) { r->eof = 1; return 1; }
|
||||
|
||||
/* fread 块扫替代逐字节 fgetc:大书扫描 O(filesize) 从全量 fgetc(逐字符 libc 调用+换行判定)
|
||||
* 改为块读+memchr,显著降低开书/建段表代价(原 fgetc 是 UI 线程被占「返回无效」成因之一)。
|
||||
* 语义与旧版一致:段 = 到达 UTF8_MAX/2 后首个 `\n` 为止(含该 `\n`)的字节;无 `\n` 则读满
|
||||
* rawsz-1;EOF 以 fread 返回 0 判定。 */
|
||||
size_t raw_n = 0;
|
||||
int got = 0;
|
||||
while (raw_n < r->rawsz - 1) {
|
||||
int c = fgetc(r->f);
|
||||
if (c == EOF) { r->eof = 1; break; }
|
||||
r->raw[raw_n++] = (uint8_t)c;
|
||||
size_t n0 = fread(r->raw + raw_n, 1, (r->rawsz - 1) - raw_n, r->f);
|
||||
if (n0 == 0) { if (feof(r->f)) r->eof = 1; break; }
|
||||
got = 1;
|
||||
/* 段落边界达成目标字节约停止(`\n` 恒为独立换行) */
|
||||
if (c == '\n' && raw_n >= RDTXT_SEG_UTF8_MAX / 2) break;
|
||||
size_t chunk_end = raw_n + n0;
|
||||
size_t from = (raw_n < RDTXT_SEG_UTF8_MAX / 2) ? (RDTXT_SEG_UTF8_MAX / 2) : raw_n;
|
||||
if (chunk_end >= RDTXT_SEG_UTF8_MAX / 2) {
|
||||
int stop = 0;
|
||||
for (size_t k = from; k < chunk_end; k++) {
|
||||
if (r->raw[k] == '\n') {
|
||||
size_t keep = k + 1; /* 本段含该 `\n` */
|
||||
if (fseek(r->f, start + (long)keep, SEEK_SET) != 0) r->eof = 1;
|
||||
raw_n = keep;
|
||||
stop = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stop) break;
|
||||
}
|
||||
raw_n = chunk_end;
|
||||
}
|
||||
if (!got) { r->eof = 1; return 1; }
|
||||
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/* core/seg.c — TXT 分段流式缓存实现(见 core/seg.h)。
|
||||
* 纯 libc(fopen/fread/fwrite/stat/remove/mkdir)+ rdenc 的码位边界对齐,无 SDK 依赖。
|
||||
*/
|
||||
#include "seg.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define SEG_MAGIC0 'R'
|
||||
#define SEG_MAGIC1 'D'
|
||||
#define SEG_MAGIC2 'S'
|
||||
#define SEG_MAGIC3 'G'
|
||||
#define SEG_VER 1u
|
||||
|
||||
static char g_base[400];
|
||||
|
||||
void rdseg_set_base(const char *base)
|
||||
{
|
||||
if (base && base[0]) {
|
||||
strncpy(g_base, base, sizeof g_base - 1);
|
||||
g_base[sizeof g_base - 1] = 0;
|
||||
} else {
|
||||
g_base[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t seg_fnv(const char *s)
|
||||
{
|
||||
uint32_t h = 2166136261u;
|
||||
for (const unsigned char *p = (const unsigned char *)s; *p; p++) { h ^= *p; h *= 16777619u; }
|
||||
return h;
|
||||
}
|
||||
|
||||
int rdseg_dir_for(const char *book_path, char *out, int outsz)
|
||||
{
|
||||
if (!book_path || !out || !g_base[0]) return -1;
|
||||
if (snprintf(out, (size_t)outsz, "%s/seg/%08x", g_base, seg_fnv(book_path)) >= outsz) return -2;
|
||||
/* mkdir -p 幂等(progress/index 同款) */
|
||||
char t[520];
|
||||
strncpy(t, out, sizeof t - 1); t[sizeof t - 1] = 0;
|
||||
for (char *c = t + 1; *c; c++) {
|
||||
if (*c == '/') { *c = 0; mkdir(t, 0755); *c = '/'; }
|
||||
}
|
||||
mkdir(t, 0755);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- 码位边界对齐 ---- */
|
||||
static int b5_trail(unsigned b) { return (b >= 0x40 && b <= 0x7E) || (b >= 0xA1 && b <= 0xFE); }
|
||||
static int gb_trail(unsigned b) { return (b >= 0x40 && b <= 0x7E) || (b >= 0x80 && b <= 0xFE); }
|
||||
|
||||
/* 返回 raw[0..len) 中 ≤ target 的最大「码位边界」cut(该处之后是完整字符;cut 越小越保守)。
|
||||
* target 越界钳到 len。 */
|
||||
static size_t seg_align(rd_enc_t enc, const uint8_t *raw, size_t len, size_t target)
|
||||
{
|
||||
if (target > len) target = len;
|
||||
if (enc == RD_ENC_UTF16LE || enc == RD_ENC_UTF16BE) {
|
||||
size_t cut = target & ~(size_t)1; /* 偶数对齐 */
|
||||
if (cut >= 2) {
|
||||
uint16_t u = (enc == RD_ENC_UTF16BE) ? (uint16_t)((raw[cut - 2] << 8) | raw[cut - 1])
|
||||
: (uint16_t)((raw[cut - 1] << 8) | raw[cut - 2]);
|
||||
if (u >= 0xD800 && u <= 0xDBFF) cut -= 2; /* 高代理在边界 → 携带到下一段 */
|
||||
}
|
||||
return cut;
|
||||
}
|
||||
size_t p = 0, last = 0;
|
||||
while (p < target) {
|
||||
unsigned n = 1;
|
||||
uint8_t b = raw[p];
|
||||
if (enc == RD_ENC_UTF8 || enc == RD_ENC_ASCII) {
|
||||
if (b < 0x80) n = 1;
|
||||
else if ((b & 0xE0) == 0xC0) n = 2;
|
||||
else if ((b & 0xF0) == 0xE0) n = 3;
|
||||
else if ((b & 0xF8) == 0xF0) n = 4;
|
||||
else n = 1;
|
||||
} else { /* GB18030 / BIG5 双字节 */
|
||||
if (b < 0x80) n = 1;
|
||||
else if (b >= 0x81 && b <= 0xFE && p + 1 < len) {
|
||||
uint8_t t = raw[p + 1];
|
||||
n = ((enc == RD_ENC_BIG5) ? b5_trail(t) : gb_trail(t)) ? 2u : 1u;
|
||||
} else n = 1;
|
||||
}
|
||||
if (p + n > target) break; /* 越过 target → 停在上一安全位 */
|
||||
p += n; last = p;
|
||||
}
|
||||
return last ? last : target;
|
||||
}
|
||||
|
||||
/* ---- manifest IO ---- */
|
||||
static int manifest_path(const char *seg_dir, char *out, int outsz)
|
||||
{
|
||||
if (!seg_dir || !seg_dir[0]) return -1;
|
||||
return (snprintf(out, (size_t)outsz, "%s/manifest.idx", seg_dir) >= outsz) ? -2 : 0;
|
||||
}
|
||||
|
||||
static int manifest_valid(const char *seg_dir, uint64_t fsize, int64_t fmtime, rd_enc_t enc, int *nsegs)
|
||||
{
|
||||
char man[560];
|
||||
if (manifest_path(seg_dir, man, sizeof man) != 0) return 0;
|
||||
FILE *mf = fopen(man, "rb");
|
||||
if (!mf) return 0;
|
||||
uint8_t magic[4];
|
||||
uint16_t ver;
|
||||
uint64_t mfs, mmt;
|
||||
uint32_t menc, mn;
|
||||
int ok = 0;
|
||||
if (fread(magic, 1, 4, mf) == 4 && magic[0] == SEG_MAGIC0 && magic[1] == SEG_MAGIC1 &&
|
||||
magic[2] == SEG_MAGIC2 && magic[3] == SEG_MAGIC3 &&
|
||||
fread(&ver, 2, 1, mf) == 1 && ver == SEG_VER &&
|
||||
fread(&mfs, 8, 1, mf) == 1 && fread(&mmt, 8, 1, mf) == 1 &&
|
||||
fread(&menc, 4, 1, mf) == 1 && fread(&mn, 4, 1, mf) == 1 &&
|
||||
mfs == fsize && mmt == (uint64_t)fmtime && menc == (uint32_t)enc && mn >= 1 && mn < 0x7fffffffu) {
|
||||
if (nsegs) *nsegs = (int)mn;
|
||||
ok = 1;
|
||||
}
|
||||
fclose(mf);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int rdseg_n(const char *seg_dir)
|
||||
{
|
||||
char man[560];
|
||||
if (manifest_path(seg_dir, man, sizeof man) != 0) return 0;
|
||||
FILE *mf = fopen(man, "rb");
|
||||
if (!mf) return 0;
|
||||
uint8_t magic[4]; uint16_t ver; uint32_t mn;
|
||||
int n = 0;
|
||||
if (fread(magic, 1, 4, mf) == 4 && magic[0] == SEG_MAGIC0 && magic[1] == SEG_MAGIC1 &&
|
||||
magic[2] == SEG_MAGIC2 && magic[3] == SEG_MAGIC3 &&
|
||||
fread(&ver, 2, 1, mf) == 1 && ver == SEG_VER &&
|
||||
fseek(mf, 8 + 8 + 4, SEEK_CUR) == 0 &&
|
||||
fread(&mn, 4, 1, mf) == 1 && mn >= 1 && mn < 0x7fffffffu) n = (int)mn;
|
||||
fclose(mf);
|
||||
return n;
|
||||
}
|
||||
|
||||
int rdseg_rawlen(const char *seg_dir, int i, size_t *len)
|
||||
{
|
||||
if (!seg_dir || !len || i < 0) return -1;
|
||||
char man[560];
|
||||
if (manifest_path(seg_dir, man, sizeof man) != 0) return -2;
|
||||
FILE *mf = fopen(man, "rb");
|
||||
if (!mf) return 1;
|
||||
uint8_t magic[4]; uint16_t ver; uint32_t mn;
|
||||
int r = 1;
|
||||
if (fread(magic, 1, 4, mf) == 4 && magic[0] == SEG_MAGIC0 &&
|
||||
fread(&ver, 2, 1, mf) == 1 && ver == SEG_VER &&
|
||||
fseek(mf, 8 + 8 + 4, SEEK_CUR) == 0 &&
|
||||
fread(&mn, 4, 1, mf) == 1 && (uint32_t)i < mn) {
|
||||
int ok = 1;
|
||||
for (int k = 0; k < i; k++) {
|
||||
uint64_t a, b;
|
||||
if (fread(&a, 8, 1, mf) != 1 || fread(&b, 8, 1, mf) != 1) { ok = 0; break; }
|
||||
}
|
||||
if (ok) {
|
||||
uint64_t a, b;
|
||||
if (fread(&a, 8, 1, mf) == 1 && fread(&b, 8, 1, mf) == 1) { *len = (size_t)b; r = 0; }
|
||||
}
|
||||
}
|
||||
fclose(mf);
|
||||
return r;
|
||||
}
|
||||
|
||||
int rdseg_build(const char *book_path, rd_enc_t enc, const char *seg_dir)
|
||||
{
|
||||
if (!book_path || !seg_dir || !g_base[0]) return 0;
|
||||
struct stat st;
|
||||
if (stat(book_path, &st) != 0) return -1;
|
||||
uint64_t fsize = (uint64_t)st.st_size;
|
||||
int64_t fmtime = (int64_t)st.st_mtime;
|
||||
if (fsize == 0) return 0; /* 空书不切 */
|
||||
|
||||
int n_cached = 0;
|
||||
if (manifest_valid(seg_dir, fsize, fmtime, enc, &n_cached)) return n_cached;
|
||||
|
||||
FILE *src = fopen(book_path, "rb");
|
||||
if (!src) return -1;
|
||||
|
||||
size_t pending_cap = RDSEG_SIZE + 65536 + 4;
|
||||
uint8_t *pending = (uint8_t *)malloc(pending_cap);
|
||||
if (!pending) { fclose(src); return -6; }
|
||||
uint8_t row[65536];
|
||||
size_t pend = 0;
|
||||
|
||||
typedef struct { uint64_t raw_off, raw_len; } ent_t;
|
||||
ent_t *ents = NULL; size_t ne = 0, cape = 0;
|
||||
uint64_t raw_off = 0;
|
||||
int seg_index = 0;
|
||||
int err = 0;
|
||||
|
||||
for (;;) {
|
||||
size_t got = fread(row, 1, sizeof row, src);
|
||||
if (got + pend > pending_cap) { err = -3; break; }
|
||||
memcpy(pending + pend, row, got);
|
||||
pend += got;
|
||||
while (pend >= RDSEG_SIZE) {
|
||||
size_t cut = seg_align(enc, pending, pend, RDSEG_SIZE);
|
||||
if (cut == 0) cut = RDSEG_SIZE; /* 无法对齐(畸形输入)→ 兜底整段 */
|
||||
char name[32]; snprintf(name, sizeof name, "seg_%03d.dat", seg_index);
|
||||
char full[600];
|
||||
if (snprintf(full, sizeof full, "%s/%s", seg_dir, name) >= (int)sizeof full) { err = -3; break; }
|
||||
FILE *o = fopen(full, "wb");
|
||||
if (!o) { err = -3; break; }
|
||||
fwrite(pending, 1, cut, o);
|
||||
fclose(o);
|
||||
if (ne >= cape) { cape = cape ? cape * 2 : 64; ent_t *no = (ent_t *)realloc(ents, cape * sizeof *no); if (!no) { err = -6; break; } ents = no; }
|
||||
ents[ne].raw_off = raw_off; ents[ne].raw_len = cut; ne++;
|
||||
raw_off += cut; seg_index++;
|
||||
memmove(pending, pending + cut, pend - cut);
|
||||
pend -= cut;
|
||||
if (err) break;
|
||||
if (pend == 0) break;
|
||||
}
|
||||
if (err) break;
|
||||
if (got < sizeof row) break; /* EOF */
|
||||
}
|
||||
fclose(src);
|
||||
|
||||
if (!err && pend > 0) { /* 末段(不足 512KB) */
|
||||
char name[32]; snprintf(name, sizeof name, "seg_%03d.dat", seg_index);
|
||||
char full[600];
|
||||
if (snprintf(full, sizeof full, "%s/%s", seg_dir, name) < (int)sizeof full) {
|
||||
FILE *o = fopen(full, "wb");
|
||||
if (!o) err = -3;
|
||||
else { fwrite(pending, 1, pend, o); fclose(o);
|
||||
if (ne >= cape) { cape = cape ? cape * 2 : 64; ent_t *no = (ent_t *)realloc(ents, cape * sizeof *no); if (!no) { err = -6; } else ents = no; }
|
||||
if (!err) { ents[ne].raw_off = raw_off; ents[ne].raw_len = pend; ne++; raw_off += pend; seg_index++; } }
|
||||
}
|
||||
}
|
||||
free(pending);
|
||||
|
||||
if (!err) {
|
||||
char man[560];
|
||||
if (manifest_path(seg_dir, man, sizeof man) == 0) {
|
||||
FILE *mf = fopen(man, "wb");
|
||||
if (!mf) err = -4;
|
||||
else {
|
||||
uint8_t magic[4] = { SEG_MAGIC0, SEG_MAGIC1, SEG_MAGIC2, SEG_MAGIC3 };
|
||||
uint16_t ver = SEG_VER;
|
||||
uint32_t uenc = (uint32_t)enc, un = (uint32_t)ne;
|
||||
fwrite(magic, 1, 4, mf); fwrite(&ver, 2, 1, mf);
|
||||
fwrite(&fsize, 8, 1, mf); fwrite(&fmtime, 8, 1, mf);
|
||||
fwrite(&uenc, 4, 1, mf); fwrite(&un, 4, 1, mf);
|
||||
for (size_t k = 0; k < ne; k++) { fwrite(&ents[k].raw_off, 8, 1, mf); fwrite(&ents[k].raw_len, 8, 1, mf); }
|
||||
fclose(mf);
|
||||
}
|
||||
} else err = -2;
|
||||
}
|
||||
free(ents);
|
||||
return err ? err : (int)ne;
|
||||
}
|
||||
|
||||
int rdseg_load(const char *seg_dir, int i, uint8_t *buf, size_t cap, size_t *len)
|
||||
{
|
||||
if (!seg_dir || !buf || !len || i < 0) return -1;
|
||||
size_t rl = 0;
|
||||
if (rdseg_rawlen(seg_dir, i, &rl) != 0) return 1;
|
||||
if (rl > cap) return -3;
|
||||
char name[32]; snprintf(name, sizeof name, "seg_%03d.dat", i);
|
||||
char full[600];
|
||||
if (snprintf(full, sizeof full, "%s/%s", seg_dir, name) >= (int)sizeof full) return -2;
|
||||
FILE *f = fopen(full, "rb");
|
||||
if (!f) return -4;
|
||||
size_t g = fread(buf, 1, rl, f);
|
||||
fclose(f);
|
||||
if (g != rl) return -5;
|
||||
*len = g;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rdseg_clear(const char *seg_dir)
|
||||
{
|
||||
if (!seg_dir || !seg_dir[0]) return;
|
||||
char man[560];
|
||||
if (manifest_path(seg_dir, man, sizeof man) == 0) remove(man);
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char n[32]; snprintf(n, sizeof n, "%s/seg_%03d.dat", seg_dir, i);
|
||||
if (remove(n) != 0) break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/* core/seg.h — TXT 分段流式缓存:把 txt(任意 rd_enc_t 编码)原始字节按 ~512KB 对齐到「码位边界」
|
||||
* 切成多个段文件(`<TF>/MeowData/os.meow.reader/seg/<path哈希>/seg_NNN.dat`)+ 一个 manifest.idx
|
||||
* (记录每段起始字节偏移 + 段长,加载时按偏移拼接/定位)。用于:
|
||||
* 1. 建索引时顺带切段落盘(复用 index 同父目录,无卡/未配置 base 时直接禁用、不持久);
|
||||
* 2. 渲染只加载「当前页所在段」这一个段文件(有界 ~512KB,不全量读整书)——治大书开书
|
||||
* CPU 100%/返回无效(`build_index` 全量建表 + `refill` 全量光栅化占用 UI 线程)。
|
||||
*
|
||||
* 纯 libc + rdenc,无 SDK 依赖;宿主可测。段内字节对齐到该编码的码位边界(UTF-8/GB18030/Big5
|
||||
* 按首字节序列,UTF-16 按偶数对齐并避免在代理对中间断开),故加载时 `rd_to_utf8` 转换不截断
|
||||
* 多字节字符(尾字完整)。
|
||||
*/
|
||||
#ifndef RDSEG_H
|
||||
#define RDSEG_H
|
||||
|
||||
#include "rdenc.h"
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 每段原始字节目标(对齐到码位边界后小幅 ±)。 */
|
||||
#define RDSEG_SIZE (512 * 1024)
|
||||
|
||||
/* 配置段缓存根 base(= <TF>/MeowData/os.meow.reader,与 index 同父),由 reader.c 注入;
|
||||
* base 为空/未设 → 全程禁用(直排不持久,等同 index 关闭)。 */
|
||||
void rdseg_set_base(const char *base);
|
||||
|
||||
/* 由 book_path 推导段目录 `<base>/seg/<path哈希>` 并 mkdir -p。
|
||||
* base 禁用 → 返回 -1(out 不动);成功 0(out 填),失败负值。 */
|
||||
int rdseg_dir_for(const char *book_path, char *out, int outsz);
|
||||
|
||||
/* 建段文件 + manifest(幂等:manifest 键 (file_size, mtime, enc) 与当前一致且完整 → 不重写,返回已有 nsegs)。
|
||||
* 成功返回段数 nsegs(>=1);base 禁用/空书 0;错误负值。 */
|
||||
int rdseg_build(const char *book_path, rd_enc_t enc, const char *seg_dir);
|
||||
|
||||
/* manifest 段数;段目录不存在/空 → 0。 */
|
||||
int rdseg_n(const char *seg_dir);
|
||||
|
||||
/* 段 i 原始字节长(manifest)。0=成功;越界 1;错误负值。 */
|
||||
int rdseg_rawlen(const char *seg_dir, int i, size_t *len);
|
||||
|
||||
/* 读段 i 原始字节到 buf(cap 须 ≥ 段长)。0=成功(*len);越界 1;错误负值。 */
|
||||
int rdseg_load(const char *seg_dir, int i, uint8_t *buf, size_t cap, size_t *len);
|
||||
|
||||
/* 删除段目录全部文件(编码切换等重建前用)。 */
|
||||
void rdseg_clear(const char *seg_dir);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* RDSEG_H */
|
||||
Reference in new issue
Block a user