图库: 修系统返回语义与打开册资源路径(M15)

- 返回手势:书库列表排除左缘 24px 系统返回带并清空残留拖拽态;library_go_up
  到 TF 根返回 0(app.c 退出 App),不再上溯系统目录;顶栏返回改为显式
  library_go_parent(访问放开,可越过 TF 根到 /)。lock 页继续自行消费返回。
- 打开册:缩略图源图改用预算解码(meow_img_load_file_dim,峰值数十 MB→~1MB);
  离开书库页停止后台探测/缩略图线程,避免与 open_book 争内存/FD;打开失败按
  errno 记日志,ENOMEM/EMFILE/ENFILE/EAGAIN/EINTR 自动重试一次;修 open_book
  自拷贝 snprintf 的未定义行为。
- 测试:test_browse_ui_host 增加 TF 根退出/顶栏上级/左缘不拖拽断言;新增
  test_open_book_host(封面图+含图子目录的父目录 → reader 打开的完整 UI 链路)。
This commit is contained in:
AskaEth committed 2026-11-05 13:03:20 +08:00
1 parent 5ac227b225
commit 3cbcaaaac1
7 files changed
+377 -13

No files matched your search

+56 -9
View File
@@ -6,8 +6,8 @@
* - 首帧只做**单层**枚举(logic/library.c:一次 opendir/readdir+lstat),
* 不做任何递归「子树含图」探测;列表行 = 当前目录的直接子目录(M11 起还包括
* .cbz/.zip 档案册,与目录自然序混排);
* - 系统目录/空目录/无图目录都列出且可进入;可一路向上到文件系统 '/',
* 左缘返回由 app.c 逐级上溯、到 '/' 才退出 App;
* - 系统目录/空目录/无图目录都列出且可进入;左缘返回按系统语义逐级上移,
* **到 TF 根即退出 App**(不再上溯系统目录);TF 根以外的上级浏览走顶栏「上级」动作;
* - 「直接含图目录=一本书」与档案册页数改为后台懒算:行仍在 PENDING 时副标题「…」,
* 后台线程用 mr_library_probe_path(单层 + mtime/size 失效缓存)算出页数/首图,
* on_tick 补位后只 invalidate 列表视口——首帧/滚动/命中判定都不读子目录/档案;
@@ -41,6 +41,7 @@
#define MR_THUMB_MAX 24
#define MR_THUMB_DIM 96
#define MR_THUMB_DECODE_DIM 256 /* 缩略图源图预算解码边长(峰值 ~1MB;整幅解码可达数十 MB) */
#define MR_TOPAPPBAR_H 56
#define MR_DEFAULT_ROW_H 56
#define MR_CACHE_MAX_FILES 64
@@ -294,7 +295,10 @@ static meow_img *thumb_load(const char *cover)
}
}
meow_img *im = meow_img_load_file(src);
/* 预算解码:封面可能是 12MP 手机照片,整幅解码峰值 ~48MB,会在低内存设备上
* 挤掉随后 reader 打开所需的 opendir/malloc(用户报告「点开父文件夹=无法打开此册」)。
* 缩略图最终只要 96px,先解到 ≤MR_THUMB_DECODE_DIM 再精修,视觉无损、峰值 ~1MB。 */
meow_img *im = meow_img_load_file_dim(src, MR_THUMB_DECODE_DIM);
if (!im) return NULL;
meow_img *small = meow_img_downscale(im, MR_THUMB_DIM);
if (small) { meow_img_free(im); im = small; }
@@ -559,7 +563,8 @@ static void update_topbar(void)
if (slash && slash[1]) title = slash + 1;
}
meow_topappbar_set_title(g_tb, title);
/* 可一路向上到文件系统 '/':到 '/' 才隐藏返回(此时左缘返回=退出 App)。 */
/* 顶栏「上级」是显式浏览动作(访问放开,可越过 TF 根上溯到 '/');
* 左缘返回是系统返回语义,TF 根=退出 App(见 library_go_up)。到 '/' 才隐藏。 */
meow_topappbar_set_back(g_tb, strcmp(g_cur_dir, "/") != 0);
}
@@ -640,7 +645,8 @@ static int load_dir(const char *dir)
char msg[200];
snprintf(msg, sizeof msg, "无法读取目录:%s", name);
show_hint(msg);
meow_log(MEOW_LOG_WARN, "library scan failed: %s", target);
meow_log(MEOW_LOG_WARN, "library scan failed: %s (errno=%d %s)", target,
errno, strerror(errno));
/* 启动首帧根目录不可读:仍建一个空列表,页面可交互(返回/回根/上溯)。 */
if (!g_lv) {
mr_library_free(&g_lib);
@@ -678,7 +684,17 @@ int library_at_root(void)
int library_go_up(void)
{
if (strcmp(g_cur_dir, "/") == 0) return 0; /* 到文件系统根:交给 app.c 退出 */
/* 系统返回语义:TF 根 = 退出 App(app.c 收到 0 后 request_exit),
* 不再上溯系统目录;文件系统 "/" 同理。 */
if (strcmp(g_cur_dir, g_root_dir) == 0) return 0;
if (strcmp(g_cur_dir, "/") == 0) return 0;
return library_go_parent();
}
/* 顶栏「上级」:显式浏览动作,访问放开——可越过 TF 根一路到 '/'。 */
int library_go_parent(void)
{
if (strcmp(g_cur_dir, "/") == 0) return 0;
char parent[MR_PATH_MAX];
if (mr_path_parent(g_cur_dir, parent, (int)sizeof parent) != 0) return 0;
if (!load_dir(parent)) {
@@ -719,10 +735,13 @@ static void library_open_entry(int idx)
if (e->probe_state == MR_LIB_PENDING) {
mr_library_probe p;
if (mr_library_probe_path(e->path, e->is_archive, cbz_archive_pages, NULL, &p) == 0)
if (mr_library_probe_path(e->path, e->is_archive, cbz_archive_pages, NULL, &p) == 0) {
mr_library_apply_probe(&g_lib, idx, &p);
else
} else {
meow_log(MEOW_LOG_WARN, "library probe failed: %s (errno=%d %s)", e->path,
errno, strerror(errno));
mr_library_mark_error(&g_lib, idx);
}
list_invalidate();
}
if (e->probe_state == MR_LIB_FAILED) {
@@ -752,7 +771,7 @@ void library_on_tap(int x, int y)
if (g_tb) {
int hit = meow_topappbar_hit(g_tb, x, y);
if (hit == MEOW_TOPBAR_BACK) { library_go_up(); return; }
if (hit == MEOW_TOPBAR_BACK) { library_go_parent(); return; }
if (g_tb_home_idx >= 0 && hit == g_tb_home_idx) { library_go_tf_root(); return; }
if (hit >= 0) { meow_view_page_push(ACT_SETTINGS); return; }
}
@@ -788,8 +807,14 @@ void library_on_swipe(int dir, int dist)
void library_on_touch_down(int x, int y)
{
if (!g_ctrl.active || !g_lv) return;
/* 每次按下都作废上一次手势的拖拽状态:热区/左缘提前返回时不能残留 g_drag,
* 否则系统返回手势的 move 仍会驱动列表滚动。 */
g_drag = 0;
g_dragged = 0;
/* 屏底 28px 是系统「上滑回桌面」热区:不从这里启动列表拖拽,避免拦截系统手势。 */
if (y >= meow_ui_height() - 28) return;
/* 左缘 24px 是系统返回手势带(与 reader 同口径):列表不启动拖拽,把滑动留给系统返回。 */
if (x < 24) return;
if (x < g_list_x || x >= g_list_x + g_list_w ||
y < g_list_y || y >= g_list_y + g_list_h) return;
g_drag = 1;
@@ -911,6 +936,7 @@ static void library_page_enter(meow_view *v, void *ctx)
hint_place();
g_drag = 0;
g_dragged = 0;
worker_start(); /* 离开时停止(见 leave),回页恢复探测/缩略图 */
update_topbar();
mr_nav_report();
meow_ui_paint_full();
@@ -923,6 +949,10 @@ static void library_page_leave(meow_view *v, void *ctx)
g_drag = 0;
g_dragged = 0;
if (g_scroll) meow_scroll_stop(g_scroll);
/* 停止后台解码:进 reader 前释放/停止潜在的大图解码,避免与 open_book 的
* opendir/malloc 抢内存(设备上「点开父文件夹=无法打开此册」的诱因之一)。
* worker_stop 会 join,保证 page_push 的 on_enter 打开册时线程已退出。 */
worker_stop();
}
static void library_page_destroy(meow_view *v, void *ctx)
@@ -1012,6 +1042,23 @@ int library_test_go_up(void)
return library_go_up();
}
int library_test_go_parent(void)
{
return library_go_parent();
}
/* 跟手拖拽是否在本次手势中被启动(左缘/屏底热区排除的回归观测口)。 */
int library_test_drag_active(void)
{
return g_drag;
}
/* 后台探测/缩略图线程是否在跑(离开书库页应停止,回页恢复)。 */
int library_test_worker_on(void)
{
return g_worker_on;
}
/* 缩略图 LRU 接缝(M11 P2-1 回归):指向当前缓存目录并同步跑一次 prune。 */
const char *library_test_cache_dir(void)
{