Files
MeowOS-For-R3P2/patch_v2/reader_app/app/shelf.c
T
AskaEth 0294553096 reader(R2): EPUB/FB2/MOBI/CBZ 加载器落地
- rdsrc 统一文档源抽象:TXT/FB2/EPUB/MOBI/CBZ 都产出「UTF-8 段序列+目录+元数据+图片」,
  与 R1 rdtxt 段契约一致;book.c 改喂 rd_src(文本)或图页(CBZ)。
- EPUB:miniz 解 ZIP → 自写 rdxml 回调解析 OPF(spine/manifest)+NCX/nav → 正文 XHTML 用
  rdhtml 逐行粗解析(坏标签/未闭合/实体容错,不建 DOM),章节按 RDSRC_SEG_MAX 分片。
- FB2:rdxml 严格解析抽 section 段落 + base64 内嵌图;段题进正文+目录。
- MOBI/AZW3:vendor libmobi 最小调用面(mobi_init→load→parse_kf7/8→parse_rawml_opt→
  flow/resources),HTML 走 rdhtml 同款粗解析;LGPL-3.0 合规见 NOTICE(版权+全文+锁定 commit)。
- CBZ:miniz 解 ZIP → 自然序图片序列,rdimg(stb_image) 解码+降采样,阅读页图页模式。
- 目录显示与跳转(EPUB NCX/FB2 section/MOBI 标题);书架开放四格式;进度按
  (段,字符偏移)(TXT/FB2/EPUB/MOBI)/页号(CBZ)。
- 宿主单测 loaders_smoke(四格式抽取/目录/图片/base64/页序)+ qemu-mipsel
  formats_smoke/book_smoke;R1 test_reader_core 58/0 不回归。
- build_reader.sh 集成 miniz/libmobi/stb/zlib 头;NOTICE 入仓;交叉 -Wall -Wextra 0 warning。

新 reader md5: e29036599300eb277b1c960a75b65dbf
2026-09-02 03:11:55 +08:00

183 lines
6.2 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* app/shelf.c — 书架:扫 TF 卡 `Books/`,识别 TXT/FB2/EPUB/MOBI/CBZ(R1 仅 TXT 可开,
* 其它显示「R2 支持」占位);卡片列表 + 最近阅读置顶(按 progress ts 降序)。
* 用 W_LISTVIEW(结构化列表)+ App 自绘标题。空态=清屏提示。
*/
#include "../reader_internal.h"
#include "progress.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#define SHELF_LIST_X 0
#define SHELF_LIST_Y 64
#define SHELF_LIST_W 480
#define SHELF_LIST_H (720 - 64)
typedef struct shelf_book {
char path[400];
char title[128];
char sub[160];
int fmt; /* 0 txt,1 fb2,2 epub,3 mobi,4 cbz */
long size;
uint64_t ts; /* 最近阅读 unix;无=0 */
} shelf_book;
static shelf_book g_books[256];
static int g_nbooks;
static int g_active;
static meow_widget *g_list;
static uint32_t g_bg, g_fg, g_dim;
static char g_notice[160]; /* 空态/提示 */
static int g_notice_ms; /* 剩余显示 ms(0=无) */
static const char *fmt_name(int f)
{
switch (f) { case 0: return "TXT"; case 1: return "FB2"; case 2: return "EPUB";
case 3: return "MOBI"; case 4: return "CBZ"; default: return "?"; }
}
static int fmt_from_ext(const char *name)
{
const char *d = strrchr(name, '.');
if (!d) return -1;
if (strcasecmp(d, ".txt") == 0) return 0;
if (strcasecmp(d, ".fb2") == 0) return 1;
if (strcasecmp(d, ".epub") == 0) return 2;
if (strcasecmp(d, ".mobi") == 0) return 3;
if (strcasecmp(d, ".cbz") == 0) return 4;
return -1;
}
static void fmt_size(char *out, int outsz, long s)
{
if (s >= 1 << 20) snprintf(out, outsz, "%.1fMB", (double)s / (1 << 20));
else if (s >= 1 << 10) snprintf(out, outsz, "%.0fKB", (double)s / (1 << 10));
else snprintf(out, outsz, "%ldB", s);
}
static int cmp_recent_then_name(const void *a, const void *b)
{
const shelf_book *x = (const shelf_book *)a, *y = (const shelf_book *)b;
if (x->ts != y->ts) return (x->ts < y->ts) ? 1 : -1; /* 最近在前 */
return strcasecmp(x->title, y->title);
}
static void scan_books(void)
{
const char *dir = READER_SD_ROOT "/Books";
g_nbooks = 0;
DIR *dp = opendir(dir);
if (!dp) {
snprintf(g_notice, sizeof g_notice, "未找到 %s 目录,请放入书籍(TXT)", dir);
g_notice_ms = 4000;
return;
}
struct dirent *de;
while ((de = readdir(dp)) && g_nbooks < 256) {
if (de->d_name[0] == '.') continue;
int f = fmt_from_ext(de->d_name);
if (f < 0) continue;
shelf_book *b = &g_books[g_nbooks];
if (snprintf(b->path, sizeof b->path, "%s/%s", dir, de->d_name) >= (int)sizeof b->path) continue;
strncpy(b->title, de->d_name, sizeof b->title - 1); b->title[sizeof b->title - 1] = 0;
char *dot = strrchr(b->title, '.');
if (dot) *dot = 0; /* 标题去扩展名 */
b->fmt = f;
struct stat st;
b->size = (stat(b->path, &st) == 0) ? (long)st.st_size : 0;
rdprog p;
b->ts = (rdprog_load(b->path, &p) == 1) ? p.ts : 0;
char sz[24]; fmt_size(sz, sizeof sz, b->size);
snprintf(b->sub, sizeof b->sub, "%s · %s%s", fmt_name(f), sz,
b->ts ? " · 读" : "");
g_nbooks++;
}
closedir(dp);
qsort(g_books, (size_t)g_nbooks, sizeof(shelf_book), cmp_recent_then_name);
}
/* listview 行源 */
static int shelf_list_src(int idx, meow_list_item *out, void *ctx)
{
(void)ctx;
if (idx < 0 || idx >= g_nbooks) return 0;
const shelf_book *b = &g_books[idx];
out->icon_kind = MEOW_LIST_ICON_FILE;
out->title = b->title;
out->subtitle = b->sub;
out->right = MEOW_LIST_RIGHT_ARROW;
out->enabled = 1;
out->on = 0;
return 1;
}
static void shelf_list_tapcb(int idx, int op, void *ctx)
{
(void)op; (void)ctx;
if (idx < 0 || idx >= g_nbooks) return;
reader_open(g_books[idx].path);
}
/* view 生命周期 */
static void shelf_on_create(meow_view *v, void *ctx) { (void)v; (void)ctx; }
static void shelf_on_enter(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
g_active = 1;
scan_books(); /* 回到书架即重扫(新增/最近读置顶刷新) */
const meow_theme *th = meow_theme_get();
g_bg = th->bg; g_fg = th->fg; g_dim = th->dim;
/* 无控件时重建列表(首次/回到书架) */
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
g_list = meow_listview_create(SHELF_LIST_X, SHELF_LIST_Y, SHELF_LIST_W, SHELF_LIST_H,
g_nbooks, shelf_list_src, NULL, MEOW_LIST_SEL_SINGLE);
meow_listview_set_on_tap(g_list, shelf_list_tapcb, NULL);
shelf_redraw();
}
static void shelf_on_leave(meow_view *v, void *ctx) { (void)v; (void)ctx; g_active = 0; }
static void shelf_on_destroy(meow_view *v, void *ctx)
{
(void)v; (void)ctx;
if (g_list) { meow_widget_free(g_list); g_list = NULL; }
}
meow_view *shelf_view_create(void)
{
static meow_view_hooks hooks = { shelf_on_create, shelf_on_enter, shelf_on_leave,
shelf_on_destroy, NULL, NULL, NULL };
return meow_view_create(ACT_SHELF, MEOW_VIEW_ACTIVITY, &hooks, NULL);
}
int shelf_is_active(void) { return g_active; }
void shelf_redraw(void)
{
const meow_theme *th = meow_theme_get();
g_bg = th->bg; g_fg = th->fg; g_dim = th->dim;
meow_ui_clear(g_bg);
meow_ui_text(20, 22, "书架", 28, g_fg);
/* 副标题:格式化说明 */
char sub[96]; snprintf(sub, sizeof sub, "Books/ · %d 本", g_nbooks);
meow_ui_text(20, 24 + 34, sub, 20, g_dim);
if (g_list) meow_widget_draw(g_list);
if (g_nbooks == 0) {
meow_ui_text(30, 320, "书架为空:请往 TF 卡 Books/ 放书籍", 20, g_dim);
meow_ui_text(30, 348, "支持 TXT / FB2 / EPUB / MOBI / CBZ", 20, g_dim);
}
if (g_notice_ms > 0) {
meow_ui_fill_rect(0, 620, 480, 44, g_bg);
meow_ui_text(20, 630, g_notice, 20, th->primary);
}
meow_ui_flush();
}
int shelf_on_tap(int x, int y)
{
if (g_list && g_nbooks > 0) {
int idx = meow_listview_tap_at(g_list, x, y);
if (idx >= 0) { shelf_redraw(); return 1; } /* 选中已由 SDK 标脏;重绘 */
}
return 0;
}