图库: 修系统返回语义与打开册资源路径(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)
{
+29 -2
View File
@@ -42,6 +42,7 @@
#include "logic/reader/page_cache.h"
#include "logic/reader/nav.h"
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@@ -79,6 +80,8 @@ static mr_book_handle g_handle; /* 目录册或档案册(M11 统一开册
static mr_book g_book;
static int g_count; /* 0=空册/不可读 */
static int g_open_err;
static uint64_t g_open_retry_at; /* 资源类打开失败(ENOMEM/EMFILE…)的自动重试时刻 */
static int g_open_retry_used; /* 1=本次进入已用过自动重试(避免反复重试) */
static int g_page; /* 翻页模式当前页;条漫模式=最近落位页 */
static int g_mode = MR_MODE_PAGED;
@@ -1158,6 +1161,7 @@ static void open_book_reset_state(void)
g_gen++; /* 作废旧册的异步结果 */
g_count = 0;
g_open_err = 0;
g_open_retry_at = 0;
g_page = 0;
g_woff = 0;
g_zoom_pct = 100;
@@ -1179,7 +1183,9 @@ static int open_book(const char *dir, int start_page, int mode)
{
if (!dir || !dir[0]) return 0;
open_book_reset_state();
snprintf(g_dir, sizeof g_dir, "%s", dir);
/* reader_page_enter 传入的 dir 就是 g_dir:自拷贝是 snprintf 的未定义行为,
* 其它调用(册间衔接)才需要复制。 */
if (dir != g_dir) snprintf(g_dir, sizeof g_dir, "%s", dir);
g_mode = mr_mode_valid(mode) ? mode : MR_MODE_PAGED;
int maxdim = g_vw > g_vh ? g_vw : g_vh;
@@ -1188,10 +1194,19 @@ static int open_book(const char *dir, int start_page, int mode)
int n = mr_book_open_auto(&g_book, &g_handle, g_dir, maxdim,
reader_load_page, reader_unload_page, NULL);
if (n < 0) {
int err = errno; /* opendir/malloc 失败原因(设备排障用) */
g_open_err = 1;
worker_start();
update_title();
meow_log(MEOW_LOG_WARN, "reader open failed: %s", g_dir);
meow_log(MEOW_LOG_WARN, "reader open failed: %s (errno=%d %s)", g_dir, err,
strerror(err));
/* 资源类失败(内存/FD 瞬时紧张)自动重试一次:library 页已在 on_leave 停止
* 后台解码,稍后内存回收后重开即可成功;权限/路径错误不重试。 */
if ((err == ENOMEM || err == EMFILE || err == ENFILE ||
err == EAGAIN || err == EINTR) && !g_open_retry_used) {
g_open_retry_used = 1;
g_open_retry_at = meow_ui_time_ms() + 200;
}
meow_ui_invalidate(0, 0, g_vw, g_vh);
return 0;
}
@@ -1602,6 +1617,15 @@ static void reader_tick(void)
int had_current = slot_find(current_page()) != NULL;
if (results_drain() && !had_current && slot_find(current_page()) && !g_dragging)
meow_ui_invalidate(0, 0, g_vw, g_vh);
/* 打开册的瞬时资源失败:稍后自动重试一次(进入本页时已停 library 后台解码)。 */
if (g_open_err && g_open_retry_at && now >= g_open_retry_at) {
g_open_retry_at = 0;
int mode = mr_cont_get_mode(g_dir, MR_MODE_PAGED);
int page = mr_cont_get_page(g_dir, 0);
open_book(g_dir, page, mode);
return;
}
if (g_fail_paint) {
g_fail_paint = 0;
if (!g_dragging) meow_ui_invalidate(0, 0, g_vw, g_vh);
@@ -1712,6 +1736,8 @@ static void reader_state_reset(void)
slots_free_all(); /* 静态槽位初值是 idx=0/img=NULL,先清空 */
g_count = 0;
g_open_err = 0;
g_open_retry_at = 0;
g_open_retry_used = 0;
g_page = 0;
g_mode = MR_MODE_PAGED;
g_fit = MR_FIT_WIDTH;
@@ -1869,6 +1895,7 @@ void mr_reader_open_dir(const char *path)
int mr_reader_ui_mode(void) { return g_mode; }
int mr_reader_ui_page(void) { return current_page(); }
int mr_reader_ui_count(void) { return g_count; }
int mr_reader_ui_open_err(void) { return g_open_err; }
int mr_reader_ui_jump_open(void) { return g_jump_open; }
int mr_reader_ui_jump_pending(void) { return mr_jump_active(&g_jump); }
#endif