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:
AskaEth committed 2026-11-04 19:47:10 +08:00
1 parent a64ae06eb6
commit f40abbd494
17 files changed
+1069 -70

No files matched your search

+189 -14
View File
@@ -8,6 +8,7 @@
#include "book.h"
#include "rdsrc.h"
#include "rdtxt.h"
#include "seg.h"
#include "rdlayout.h"
#include "rdimg.h"
#include "../engine/rdft.h"
@@ -44,6 +45,12 @@ struct rd_book {
uint32_t *seg_cps; size_t seg_cps_n, seg_cps_cap;
rd_line *seg_lines; rd_page *seg_pages; int seg_nlines, seg_npages;
int seg_cache_idx;
/* R3.2 分段流式(TXT):段目录 + 全局段数 + 已建段数 + 段/页表容量(增量补齐用) */
char seg_dir[400];
int seg_n; /* 段文件总数(manifest);fallback = nsegs(全建) */
int seg_built; /* 已布局段数;fallback = nsegs */
int segs_cap, pages_cap; /* segs[]/pages[] 当前容量(只在 seg 路径增量增长) */
};
/* ---- 页索引持久化(MeowData;见 rd_book_set_index_dir) ----
@@ -60,6 +67,15 @@ void rd_book_set_index_dir(const char *dir)
else g_index_dir[0] = 0;
}
/* R3.2:分段流式缓存根(MeowData/os.meow.reader)。存到 g_seg_base 并同步 rdseg。 */
static char g_seg_base[400];
void rd_book_set_seg_base(const char *base)
{
if (base && base[0]) { strncpy(g_seg_base, base, sizeof g_seg_base - 1); g_seg_base[sizeof g_seg_base - 1] = 0; }
else g_seg_base[0] = 0;
rdseg_set_base(g_seg_base);
}
#define IDX_MAGIC0 'R'
#define IDX_MAGIC1 'I'
#define IDX_MAGIC2 'D'
@@ -284,15 +300,117 @@ static int build_index(rd_book *b)
return 0;
}
/* ---- R3.2 分段流式(TXT):把一段 UTF-8 布局并追加到 flat 页表 ---- */
static int append_seg_pages(rd_book *b, int seg_idx, const uint8_t *utf8, size_t ulen)
{
uint32_t *cps = NULL; size_t n = 0;
if (rd_utf8_to_cp(utf8, ulen, &cps, &n) != RD_ENC_OK) return -5;
rd_line *lines = (rd_line *)malloc((n ? n : 1) * sizeof(rd_line));
rd_page *pages = (rd_page *)malloc((n ? n : 1) * sizeof(rd_page));
if (!lines || !pages) { free(lines); free(pages); free(cps); return -6; }
rd_layout_in in;
rd_layout_out out;
in.chars = cps; in.n = (int32_t)n; in.fs = b->fs; in.line_scale = b->ls;
in.page_w = b->content_w; in.page_h = b->content_h; in.indent = b->fs;
in.meas = book_meas; in.mctx = b;
out.lines = lines; out.pages = pages; out.max_lines = (int)(n ? n : 1); out.max_pages = (int)(n ? n : 1);
int lrc = rd_layout(&in, &out);
free(cps);
if (lrc != RD_LAYOUT_OK) { free(lines); free(pages); return -4; }
b->segs[seg_idx].page_start = b->npages;
b->segs[seg_idx].page_count = out.npages;
for (int p = 0; p < out.npages; p++) {
if (b->npages >= b->pages_cap) {
int nc = b->pages_cap ? b->pages_cap * 2 : 1024;
rd_book_page *np = (rd_book_page *)realloc(b->pages, (size_t)nc * sizeof(rd_book_page));
if (!np) { free(lines); free(pages); return -6; }
b->pages = np; b->pages_cap = nc;
}
b->pages[b->npages].seg = seg_idx;
b->pages[b->npages].seg_char = out.pages[p].first_char;
b->npages++;
}
free(lines); free(pages);
return 0;
}
/* ---- R3.2 段路径:把 [seg_built, upto_seg] 逐段(512KB 段文件 → utf8 → 布局)补齐进 flat 页表。
* 幂等:已建段跳过。开书只建到目标(页 0/保存进度),翻页/跳转再按需补齐 —— 免整书一次性布局。 */
static int seg_ensure_built(rd_book *b, int upto_seg)
{
if (!b->seg_dir[0] || b->fmt != RD_FMT_TXT) return 0;
int total = b->seg_n > 0 ? b->seg_n : 1;
if (upto_seg >= total) upto_seg = total - 1;
if (upto_seg < 0) upto_seg = 0;
int built_any = 0;
while (b->seg_built <= upto_seg) {
int idx = b->seg_built;
size_t rawlen = 0;
if (rdseg_rawlen(b->seg_dir, idx, &rawlen) != 0) break;
if (b->segs_cap <= idx) {
int nc = b->segs_cap ? b->segs_cap * 2 : 64;
if (nc <= idx) nc = idx + 1;
seg_t *ns = (seg_t *)realloc(b->segs, (size_t)nc * sizeof(seg_t));
if (!ns) return -6;
b->segs = ns; b->segs_cap = nc;
}
size_t cap = rawlen * 3 / 2 + 16;
if (cap < RDTXT_SEG_UTF8_MAX + 8) cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8 || b->seg_utf8_cap < cap) {
uint8_t *t = (uint8_t *)malloc(cap);
if (!t) return -6;
free(b->seg_utf8); b->seg_utf8 = t; b->seg_utf8_cap = cap;
}
uint8_t *raw = (uint8_t *)malloc(rawlen ? rawlen : 1);
if (!raw) return -6;
size_t rn = 0;
if (rdseg_load(b->seg_dir, idx, raw, rawlen, &rn) != 0) { free(raw); return -4; }
size_t ulen = 0;
int rc = rd_to_utf8(b->enc, raw, rn, b->seg_utf8, cap, &ulen);
free(raw);
if (rc == RD_ENC_ERR_BUFSZ) return -2;
if (rc != RD_ENC_OK) return -3;
int prc = append_seg_pages(b, idx, b->seg_utf8, ulen);
if (prc != 0) return prc;
b->seg_built++;
built_any = 1;
}
b->nsegs = b->seg_built;
if (built_any) index_cache_save(b);
return 0;
}
/* ---- 正文重显 + 布局到复用缓冲 ---- */
static int load_seg(rd_book *b, int seg_idx)
{
if (seg_idx == b->seg_cache_idx) return 0;
size_t cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8) b->seg_utf8 = (uint8_t *)malloc(cap);
if (!b->seg_utf8) return -6;
size_t ulen = 0;
if (rd_src_seg(b->src, seg_idx, b->seg_utf8, cap, &ulen) != 0) return -4;
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT) {
/* R3.2 段路径:只读当前页所在 512KB 段文件(有界,不全量读整书) */
size_t rawlen = 0;
if (rdseg_rawlen(b->seg_dir, seg_idx, &rawlen) != 0) return -4;
size_t cap = rawlen * 3 / 2 + 16;
if (cap < RDTXT_SEG_UTF8_MAX + 8) cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8 || b->seg_utf8_cap < cap) {
uint8_t *t = (uint8_t *)malloc(cap);
if (!t) return -6;
free(b->seg_utf8); b->seg_utf8 = t; b->seg_utf8_cap = cap;
}
uint8_t *raw = (uint8_t *)malloc(rawlen ? rawlen : 1);
if (!raw) return -6;
size_t rn = 0;
int rc = rdseg_load(b->seg_dir, seg_idx, raw, rawlen, &rn);
if (rc != 0) { free(raw); return -4; }
int r2 = rd_to_utf8(b->enc, raw, rn, b->seg_utf8, cap, &ulen);
free(raw);
if (r2 == RD_ENC_ERR_BUFSZ) return -2;
if (r2 != RD_ENC_OK) return -3;
} else {
size_t cap = RDTXT_SEG_UTF8_MAX + 8;
if (!b->seg_utf8) b->seg_utf8 = (uint8_t *)malloc(cap);
if (!b->seg_utf8) return -6;
if (rd_src_seg(b->src, seg_idx, b->seg_utf8, cap, &ulen) != 0) return -4;
}
size_t n = 0;
free(b->seg_cps); b->seg_cps = NULL;
if (rd_utf8_to_cp(b->seg_utf8, ulen, &b->seg_cps, &n) != RD_ENC_OK) return -5;
@@ -348,10 +466,35 @@ int rd_book_open(rd_book **b, const char *path, const char *font_path,
if (font_path && rdft_load_font(x->ft, x->font) != 0) { rdft_free(x->ft); x->ft = NULL; goto close; }
if (x->ft) rdft_set_px(x->ft, fs);
x->seg_cache_idx = -1;
/* 页索引缓存命中(书+字号+版面+编码+字体未变)→ 免整书重排,秒开;未命中 → 重排并落盘。 */
if (index_cache_load(x) != 1) {
if (build_index(x) != 0) goto close;
index_cache_save(x);
int seg_mode = 0;
/* R3.2 分段流式(TXT 且段 base 已配置):开书切 512KB 段文件,逐段按需布局(不全量)。 */
if (g_seg_base[0] && x->fmt == RD_FMT_TXT) {
char sdir[600];
if (rdseg_dir_for(x->path, sdir, sizeof sdir) == 0) {
strncpy(x->seg_dir, sdir, sizeof x->seg_dir - 1); x->seg_dir[sizeof x->seg_dir - 1] = 0;
int ns = rdseg_build(x->path, x->enc, x->seg_dir);
if (ns > 0) { x->seg_n = ns; seg_mode = 1; }
}
}
if (seg_mode) {
/* 命中缓存(书+版面+编码+字体未变)→ 载入已建 (segs,pages);否则仅布局段 0 起步,
* 翻页/跳转再 seg_ensure_built 补齐(治大书开书整表布局卡 UI 线程)。 */
x->segs = NULL; x->pages = NULL; x->nsegs = x->npages = 0;
x->seg_built = 0; x->segs_cap = x->pages_cap = 0;
if (index_cache_load(x) != 1) {
if (seg_ensure_built(x, 0) != 0) goto close;
}
if (x->nsegs < 1) { /* 段 0 空(理论)→ 兜底再建 */
if (seg_ensure_built(x, 0) != 0) goto close;
}
x->segs_cap = x->nsegs; x->pages_cap = x->npages;
x->seg_built = x->nsegs; /* 缓存命中时恢复到已建段数 */
} else {
/* 页索引缓存命中(书+字号+版面+编码+字体未变)→ 免整书重排,秒开;未命中 → 重排并落盘。 */
if (index_cache_load(x) != 1) {
if (build_index(x) != 0) goto close;
index_cache_save(x);
}
}
} else {
int pages = rd_src_pages(x->src);
@@ -410,6 +553,7 @@ int rd_book_set_enc(rd_book *b, rd_enc_t enc)
int seg; int32_t ch;
rd_book_anchor(b, &seg, &ch);
rd_enc_t oldenc = b->enc;
rd_src *ns = NULL;
if (rd_src_open_with_enc(&ns, b->path, &enc) != 0) return -2; /* 旧 src 未动 */
rd_src_free(b->src); b->src = ns; /* 换新 */
@@ -418,9 +562,17 @@ int rd_book_set_enc(rd_book *b, rd_enc_t enc)
/* 重建索引(复用现值 fs/ls/content_*),再回锚 */
free(b->segs); free(b->pages); b->segs = NULL; b->pages = NULL;
b->nsegs = b->npages = 0;
if (build_index(b) != 0) return -3; /* 新 src 有效但索引空 → 渲染返回 -1,安全降级 */
index_cache_save(b); /* 编码切换后重新落盘(键含 enc,与旧缓存并存不冲突) */
b->nsegs = b->npages = 0; b->seg_built = 0; b->segs_cap = b->pages_cap = 0;
if (b->seg_dir[0]) {
/* R3.2 段路径:编码变了 → 段文件边界/解码不同 → 重建段文件 + 按需布局(段 0 起步) */
if (oldenc != b->enc) rdseg_clear(b->seg_dir);
b->seg_n = rdseg_build(b->path, b->enc, b->seg_dir);
if (seg_ensure_built(b, 0) != 0) return -3;
index_cache_save(b);
} else {
if (build_index(b) != 0) return -3; /* 新 src 有效但索引空 → 渲染返回 -1,安全降级 */
index_cache_save(b); /* 编码切换后重新落盘(键含 enc,与旧缓存并存不冲突) */
}
return rd_book_jump_to_anchor(b, seg, ch, b->fs, b->ls);
}
@@ -437,6 +589,12 @@ int rd_book_turn(rd_book *b, int delta)
if (!b || b->npages <= 0) return 0;
int np = b->cur + delta;
if (np < 0) np = 0;
/* R3.2 段路径:翻到已建表尾之外 → 按需补下一段(一次翻页只补到够用,不全量) */
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && np >= b->npages && b->seg_built < b->seg_n) {
while (np >= b->npages && b->seg_built < b->seg_n) {
if (seg_ensure_built(b, b->seg_built) != 0) break;
}
}
if (np >= b->npages) np = b->npages - 1;
b->cur = np;
return np;
@@ -445,6 +603,12 @@ int rd_book_turn(rd_book *b, int delta)
int rd_book_set_page(rd_book *b, int page)
{
if (!b || b->npages <= 0) return 0;
/* R3.2 段路径:跳到已建表外 → 按需补段到目标页(跳到哪建到哪) */
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && page >= b->npages && b->seg_built < b->seg_n) {
while (page >= b->npages && b->seg_built < b->seg_n) {
if (seg_ensure_built(b, b->seg_built) != 0) break;
}
}
if (page < 0) page = 0;
if (page >= b->npages) page = b->npages - 1;
b->cur = page;
@@ -543,9 +707,20 @@ int rd_book_jump_to_anchor(rd_book *b, int seg, int32_t ch, int fs, int ls)
seg_t *nos = b->segs; int n_oldsegs = b->nsegs;
rd_book_page *nop = b->pages; int n_oldpages = b->npages;
b->segs = NULL; b->pages = NULL; b->nsegs = b->npages = 0;
if (build_index(b) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; }
free(nos); free(nop);
index_cache_save(b); /* 字号/行距改变后重新落盘(键含 fs/ls) */
if (b->seg_dir[0] && b->fmt == RD_FMT_TXT) {
/* R3.2 段路径:只重布局到含锚的段(不全量;段文件不变,仅页表变) */
b->seg_built = 0; b->segs_cap = b->pages_cap = 0;
if (seg_ensure_built(b, seg) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; }
free(nos); free(nop);
index_cache_save(b);
} else {
if (build_index(b) != 0) { b->segs = nos; b->pages = nop; b->nsegs = n_oldsegs; b->npages = n_oldpages; return -5; }
free(nos); free(nop);
index_cache_save(b); /* 字号/行距改变后重新落盘(键含 fs/ls) */
}
} else if (b->seg_dir[0] && b->fmt == RD_FMT_TXT && seg >= b->seg_built) {
/* 锚在未建段 → 按需补齐(重开续读/目录跳转到后部) */
if (seg_ensure_built(b, seg) != 0) return -5;
}
int target = 0;
for (int p = 0; p < b->npages; p++) {
+5
View File
@@ -68,6 +68,11 @@ int rd_book_set_enc(rd_book *b, rd_enc_t enc);
* 由 reader.c 在 open 前建目录并注入;书/字号/行距/版面/编码/字体任一变化时缓存自动失效重排。 */
void rd_book_set_index_dir(const char *dir);
/* R3.2:TXT 分段流式缓存根(MeowData/os.meow.reader,与 index 同父)。base 为空 → 禁分段(退回全量直排)。
* 由 reader.c 在 open 前注入;book.c 据此派生 <base>/seg/<path哈希> 段目录,开书时把 txt 切 512KB
* 码位对齐段文件,渲染只加载当前页所在段(不全量读整书)——治大书开书 CPU100%/返回无效。 */
void rd_book_set_seg_base(const char *base);
/* 字体/几何 */
int rd_book_font(rd_book *b); /* 当前字号 */
int rd_book_ls(rd_book *b); /* 行距% */
+17 -2
View File
@@ -56,6 +56,8 @@ static uint32_t g_page_bg, g_page_fg; /* 正文页底/字色(24bit) */
static char g_chapter[128]; /* 顶栏章节名(TXT 无章节=文件名) */
static int g_show_header, g_show_footer; /* 页头条/底条显隐(rdconf.rd.header/footer) */
static volatile int g_volpage_pending; /* 系统翻页信号待消费(IPC 线程置位,UI tick 消费,E) */
static int g_prog_dirty; /* 阅读进度变更待写(翻页/刮擦/字号/编码;R3.2 定时落盘防崩溃丢进度) */
static uint64_t g_prog_last_save_ms; /* 上次进度落盘时刻(tick 节流) */
#define RD_READER_DEFAULT_FS 20
#define RD_READER_DEFAULT_LS 130
@@ -176,6 +178,7 @@ static void reader_reflow(void)
if (g_anim) { meow_anim_cancel(g_anim); g_anim = NULL; } /* 重排/雷字号时终止翻页动画 */
int seg = 0; int32_t ch = 0;
rd_book_anchor(g_book, &seg, &ch); /* 记录当前锚,重排后回锚 */
g_prog_dirty = 1;
rd_book_jump_to_anchor(g_book, seg, ch, g_fs, g_ls);
reader_refill_page();
reader_redraw();
@@ -192,6 +195,7 @@ static void reader_reflow_persist(void)
p.enc = (g_book && !rd_book_is_image(g_book)) ? (int)g_enc_cycle[g_enc_idx] : -1;
p.ts = (uint64_t)time(NULL);
rdprog_save(rd_book_path(g_book), &p);
g_prog_dirty = 0;
}
/* ---------- 渲染(W_CUSTOM render_cb) ---------- */
@@ -562,13 +566,15 @@ static void reader_turn_animated(int dir)
/* 无页位图(atlas 兜底):瞬切 */
if (!(g_has_font || rd_book_is_image(g_book)) || !g_page_buf[RD_PAGE_SLOT_CUR]) {
rd_book_turn(g_book, dir);
g_prog_dirty = 1;
reader_refill_page(); reader_redraw(); reader_reflow_persist();
return;
}
if (!g_anim_old) g_anim_old = (uint16_t *)malloc((size_t)g_pw * g_ph * 2);
if (!g_anim_old) { rd_book_turn(g_book, dir); reader_refill_page(); reader_redraw(); return; }
if (!g_anim_old) { rd_book_turn(g_book, dir); g_prog_dirty = 1; reader_refill_page(); reader_redraw(); return; }
memcpy(g_anim_old, g_page_buf[RD_PAGE_SLOT_CUR], (size_t)g_pw * g_ph * 2);
rd_book_turn(g_book, dir);
g_prog_dirty = 1;
reader_refill_page();
g_anim_dir = dir;
meow_anim_desc d = { 0.0f, 1.0f, 140, MEOW_EASE_OUT, reader_anim_update, reader_anim_done, NULL };
@@ -591,6 +597,7 @@ static void reader_scrub_jump(int page)
int np = rd_book_pages(g_book);
if (np <= 0) return;
rd_book_set_page(g_book, page);
g_prog_dirty = 1;
reader_refill_page();
reader_redraw(); /* 整页落定:正文 + 顶栏 + 刮擦条 */
reader_reflow_persist();
@@ -634,7 +641,8 @@ static void reader_scrub_begin(int x, int y)
/* ---------- 打开 ---------- */
/* R3.1:页索引缓存目录 = <TF>/MeowData/os.meow.reader/index。由 meow_config_dir
* (<TF>/MeowData/<pkg>/data) 剥尾 "/data" → <TF>/MeowData/<pkg>,接 "index",mkdir -p 后注入。 */
* (<TF>/MeowData/<pkg>/data) 剥尾 "/data" → <TF>/MeowData/<pkg>,接 "index",mkdir -p 后注入。
* 同基座派生 R3.2 分段流式缓存根 <TF>/MeowData/os.meow.reader(book.c 再接 /seg/<hash>)。 */
static void reader_setup_index_dir(void)
{
const char *data = meow_config_dir();
@@ -653,6 +661,7 @@ static void reader_setup_index_dir(void)
for (char *c = t + 1; *c; c++) if (*c == '/') { *c = 0; mkdir(t, 0755); *c = '/'; }
mkdir(t, 0755);
rd_book_set_index_dir(dir);
rd_book_set_seg_base(base); /* R3.2:分段缓存根(book.c 派生 /seg/<hash>) */
}
int reader_open(const char *path)
@@ -832,6 +841,7 @@ void reader_on_tap(int x, int y)
if (rd_book_set_enc(g_book, g_enc_cycle[ni]) == 0) {
g_enc_idx = ni;
chapter_name();
g_prog_dirty = 1;
reader_refill_page();
reader_redraw();
reader_reflow_persist();
@@ -925,5 +935,10 @@ void reader_on_tick(uint64_t now)
if (g_book && !g_menu_open && !g_dir_open && !g_ui_open)
reader_turn_animated(dir);
}
/* R3.2:进度实时落盘(翻页/刮擦/字号/编码变更后,节流 ~1s 定时写)——意外崩溃重开不丢进度 */
if (g_prog_dirty && now - g_prog_last_save_ms >= 1000) {
g_prog_last_save_ms = now;
reader_reflow_persist();
}
if (g_notice_ms > 0) { g_notice_ms--; if (g_notice_ms == 0) reader_redraw(); }
}
+7 -3
View File
@@ -91,6 +91,10 @@ echo "==> host core test (R1)"
$HOST_CC -O2 -Wall -Wextra -Icore -I. test/test_reader_core.c core/rdenc.c core/rdlayout.c core/rdtxt.c -o test/test_reader_core_host -lm
./test/test_reader_core_host
echo "==> host seg test (R3.2)"
$HOST_CC -O2 -Wall -Wextra -Icore test/seg_smoke.c core/seg.c core/rdenc.c -o test/seg_smoke_host -lm
./test/seg_smoke_host
echo "==> host loaders test"
HOBJ="$OBJ/host"; mkdir -p "$HOBJ"
for f in vendor/miniz/miniz_tinfl.c vendor/miniz/miniz_tdef.c vendor/miniz/miniz_zip.c vendor/miniz/miniz.c; do
@@ -115,21 +119,21 @@ timeout 120 qemu-mipsel ./test/rdft_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-
# ---------- 6) qemu-mipsel 书桥冒烟(TXT + 四格式) ----------
echo "==> mipsel book smoke (TXT)"
$CC -Os -static -Wall -Wextra -Iapp -Icore -Iengine $FT_INCS $COREINC test/book_smoke.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/seg.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c engine/rdft.c \
"$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -o test/book_smoke_mipsel
timeout 120 qemu-mipsel ./test/book_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}"
echo "==> mipsel formats smoke"
$CC -Os -static -Wall -Wextra -Iapp -Icore -Iengine $FT_INCS $COREINC test/formats_smoke.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
app/book.c core/rdenc.c core/rdlayout.c core/rdtxt.c core/seg.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c \
core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c engine/rdft.c \
"$OBJ"/mz_*.o "$OBJ"/lm_*.o "$OBJ"/rdimg.o "$SYSROOT/lib/libz.a" "$FT_BUILD/libfreetype.a" -o test/formats_smoke_mipsel
timeout 150 qemu-mipsel ./test/formats_smoke_mipsel "${FONT:-$ROOT/font/src/MiSans-Regular.otf}" /tmp/rd_samples
# ---------- 7) reader 二进制(mipsel 静态) ----------
echo "==> reader binary"
CORE_L="core/rdenc.c core/rdlayout.c core/rdtxt.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c"
CORE_L="core/rdenc.c core/rdlayout.c core/rdtxt.c core/seg.c core/rdxml.c core/rdhtml.c core/rdsb.c core/rdzip.c core/rdsrc.c core/rdepub.c core/rdfb2.c core/rdmobi.c core/rdcbz.c"
ENGINE_L="engine/rdft.c"
APP_L="app.c app/shelf.c app/reader.c app/book.c app/progress.c app/settings.c app/reader_conf.c app/reader_ipc.c"
$CC -Os -static -Wall -Wextra -I../sdk -I../meow_launcherd -I. -Icore -Iapp -Iengine $FT_INCS $COREINC \
+22 -5
View File
@@ -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; }
+281
View File
@@ -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;
}
}
+53
View File
@@ -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 */
+3
View File
@@ -31,6 +31,9 @@ int main(int argc, char **argv)
write_txt(txt);
rd_book *b = NULL;
/* R3.2:启用分段流式缓存(/tmp),走「切 512KB 段 + 按需布局」路径,而非全量直排 */
rd_book_set_seg_base("/tmp/meow_seg_smoke");
rd_book_set_index_dir("/tmp/meow_seg_smoke_index");
int rc = rd_book_open(&b, txt, font, 20, 130, 480, 720, 16, 20, 62);
CHECK(rc == 0, "rd_book_open");
if (rc != 0) { printf("open rc=%d\n", rc); return 1; }
+144
View File
@@ -0,0 +1,144 @@
/* test/seg_smoke.c — os.meow.reader 分段流式缓存宿主单测(纯 C,无 SDK/Freetype)。
*
* 覆盖:
* S1 UTF-8 大文件按 ~512KB 切段:段数>1、每段为合法 UTF-8(码位对齐,不切在字符中间)、
* 拼接后 == 原文件字节。
* S2 GBK(GB18030) 大文件切段:每段 raw 在完整双字节边界、逐段 rd_to_utf8 拼接 == 整文件
* 一次转换(边界不把双字节对拆开)。
* S3 幂等:同 (size,mtime,enc) 再次 build 段数不变、不重写。
* S4 每段原始字节长与 rdseg_load 读回一致。
*/
#include "seg.h"
#include "rdenc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int g_pass = 0, g_fail = 0;
#define CHECK(cond, msg) do { if (cond) { g_pass++; } else { g_fail++; printf("FAIL %s:%d %s\n", __FILE__, __LINE__, msg); } } while (0)
/* 生成一个 utf8 大文件:CJK 3 字节 + ASCII + 换行,填充到 > 512KB */
static void write_utf8_big(const char *path)
{
FILE *f = fopen(path, "wb");
const uint8_t cjk[] = { 0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD }; /* 你好 */
size_t total = 0;
while (total < RDSEG_SIZE + 100000) {
for (int i = 0; i < 16; i++) { fwrite(cjk, 1, sizeof cjk, f); total += sizeof cjk; }
const char *line = "这是一段用于验证分段对齐的中文正文,English words 123.\n";
size_t ll = strlen(line); fwrite(line, 1, ll, f); total += ll;
}
fclose(f);
}
/* 生成一个 GBK 大文件:双字节对(你好世界)重复,填到 > 512KB */
static void write_gbk_big(const char *path)
{
FILE *f = fopen(path, "wb");
const uint8_t gbk[] = { 0xC4, 0xE3, 0xBA, 0xC3, 0xCA, 0xC0, 0xBD, 0xE7 }; /* 你好世界 */
size_t total = 0;
while (total < RDSEG_SIZE + 100000) {
for (int i = 0; i < 16; i++) { fwrite(gbk, 1, sizeof gbk, f); total += sizeof gbk; }
fputc('\n', f); total += 1;
}
fclose(f);
}
static void test_align_utf8(const char *seg_dir, const char *path)
{
/* 每段合法 UTF-8 */
int n = rdseg_n(seg_dir);
CHECK(n >= 2, "utf8 segs >= 2");
for (int i = 0; i < n; i++) {
size_t rl = 0;
CHECK(rdseg_rawlen(seg_dir, i, &rl) == 0 && rl > 0, "utf8 rawlen>0");
uint8_t *b = (uint8_t *)malloc(rl ? rl : 1);
size_t got = 0;
CHECK(rdseg_load(seg_dir, i, b, rl, &got) == 0 && got == rl, "utf8 load==rawlen");
CHECK(rd_utf8_validate(b, got) == 1, "utf8 seg valid (no mid-char split)");
free(b);
}
/* 拼接后 == 原文件字节 */
size_t fz = 0;
FILE *src = fopen(path, "rb"); fseek(src, 0, SEEK_END); fz = (size_t)ftell(src); fseek(src, 0, SEEK_SET);
uint8_t *full = (uint8_t *)malloc(fz ? fz : 1);
(void)!fread(full, 1, fz, src); fclose(src);
size_t o = 0, ok = 1;
for (int i = 0; i < n; i++) {
size_t rl = 0; rdseg_rawlen(seg_dir, i, &rl);
uint8_t *b = (uint8_t *)malloc(rl ? rl : 1);
size_t got = 0; rdseg_load(seg_dir, i, b, rl, &got);
if (o + got > fz || memcmp(full + o, b, got) != 0) ok = 0;
o += got; free(b);
}
CHECK(ok && o == fz, "utf8 concat == original bytes");
free(full);
}
static void test_gbk_roundtrip(const char *seg_dir, const char *path)
{
int n = rdseg_n(seg_dir);
CHECK(n >= 2, "gbk segs >= 2");
/* 整文件一次转换(金标准) */
FILE *src = fopen(path, "rb"); fseek(src, 0, SEEK_END); long fz = ftell(src); fseek(src, 0, SEEK_SET);
uint8_t *raw = (uint8_t *)malloc((size_t)fz ? (size_t)fz : 1);
(void)!fread(raw, 1, (size_t)fz, src); fclose(src);
uint8_t *full = (uint8_t *)malloc((size_t)fz * 2 + 64);
size_t fl = 0;
int rc = rd_gb18030_to_utf8(raw, (size_t)fz, full, (size_t)fz * 2 + 64, &fl);
CHECK(rc == RD_ENC_OK, "gbk full convert ok");
free(raw);
/* 逐段转换拼接 */
uint8_t *concat = (uint8_t *)malloc((size_t)fz * 2 + 64);
size_t o = 0, ok = 1;
for (int i = 0; i < n; i++) {
size_t rl = 0; rdseg_rawlen(seg_dir, i, &rl);
uint8_t *b = (uint8_t *)malloc(rl ? rl : 1);
size_t got = 0; rdseg_load(seg_dir, i, b, rl, &got);
uint8_t *u8 = (uint8_t *)malloc(got * 2 + 64);
size_t ul = 0;
if (rd_gb18030_to_utf8(b, got, u8, got * 2 + 64, &ul) != RD_ENC_OK) ok = 0;
if (o + ul > (size_t)fz * 2 + 64) ok = 0;
if (ok) { memcpy(concat + o, u8, ul); o += ul; }
free(u8); free(b);
}
CHECK(ok && o == fl && memcmp(concat, full, fl) == 0, "gbk per-seg convert concat == full convert");
free(concat); free(full);
}
int main(void)
{
rdseg_set_base("/tmp/meow_seg_base");
char dir[600];
const char *u8 = "/tmp/seg_u8.txt", *gbk = "/tmp/seg_gbk.txt";
write_utf8_big(u8); write_gbk_big(gbk);
/* UTF-8 */
CHECK(rdseg_dir_for(u8, dir, sizeof dir) == 0, "utf8 dir_for");
int n = rdseg_build(u8, RD_ENC_UTF8, dir);
CHECK(n >= 2, "utf8 build nsegs>=2");
test_align_utf8(dir, u8);
/* 幂等:同键再建段数不变 */
CHECK(rdseg_build(u8, RD_ENC_UTF8, dir) == n, "utf8 build idempotent");
/* GBK */
CHECK(rdseg_dir_for(gbk, dir, sizeof dir) == 0, "gbk dir_for");
n = rdseg_build(gbk, RD_ENC_GB18030, dir);
CHECK(n >= 2, "gbk build nsegs>=2");
test_gbk_roundtrip(dir, gbk);
/* 大文件不切(清空再建,确保得到多个段也覆盖单段路径) */
{
char small[600]; rdseg_set_base("/tmp/meow_seg_base2");
const char *tiny = "/tmp/seg_tiny.txt";
FILE *f = fopen(tiny, "wb"); fwrite("hello 你好 world\n", 1, strlen("hello 你好 world\n"), f); fclose(f);
CHECK(rdseg_dir_for(tiny, small, sizeof small) == 0, "tiny dir");
int tn = rdseg_build(tiny, RD_ENC_UTF8, small);
CHECK(tn == 1, "tiny build nsegs==1");
size_t rl = 0; CHECK(rdseg_rawlen(small, 0, &rl) == 0 && rl == strlen("hello 你好 world\n"), "tiny rawlen");
}
remove(u8); remove(gbk); remove("/tmp/seg_tiny.txt");
printf("seg_smoke: %d pass, %d fail\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}