ncm search: 历史+单曲/歌单分类+分页+误触修复+不覆写歌单上下文 (§23)

- 搜索历史:进页自动弹键盘;meow_config 持久化 ≤12 去重最新在前;空态=历史列表(点搜/左滑删/清空)
- 结果分类单曲/歌单页签:独立结果集+独立滚动;歌单行进详情(playlist_open_pl);单曲行播放
- 触底分页:offset 累加 limit=30/页 cloudsearch 同参数;每类各记 offset
- 误触抑制:g_press_row + g_press_delta>10(对齐 home/playlist §13.3)
- 结果改独立数组(不写 g_st.songs[]);播放搜索单曲建搜索队列(queue_src=SEARCH),不弄丢歌单窗口
- core cloudsearch 扩展 type/offset/limit/total + 歌单解析
- bench/search_sim.c 离线回归(历史/独立集/翻页 offset/队列上下文);host searchtap 误触自检

验证:mipsel -Wall -Wextra 0 warning;core host/mipsel PASS;search_sim PASS;
宿主离屏 search(单曲)/searchpl(歌单)/searchhist(空态历史)/searchtap(误触) 均正常;0 真实 NCM 请求。
This commit is contained in:
MeowOS committed 2026-09-01 19:21:16 +08:00
1 parent d4928224a5
commit 1e97dbf54a
15 files changed
+1152 -121

No files matched your search

+229 -3
View File
@@ -10,6 +10,11 @@
/* 滑动窗口互斥(REPORT §17):窗口读写(滑窗/物化/拷贝)全走此锁,见下方窗口节注释。 */
static pthread_mutex_t g_song_mu = PTHREAD_MUTEX_INITIALIZER;
/* 搜索结果独立数组互斥(§23):g_st.search_songs/search_pls 读写 + queue_src 读。 */
static pthread_mutex_t g_search_mu = PTHREAD_MUTEX_INITIALIZER;
static void appstate_srch_hist_init(void); /* §23:读入本地搜索历史(appstate_init 首调) */
int appstate_init(void)
{
const char *dd = getenv("MEOW_DATA_DIR");
@@ -37,6 +42,9 @@ int appstate_init(void)
account_load(); /* §20:恢复会员态缓存(account.json;无文件则 NCM_VIP_NONE) */
g_st.queue_src = NCM_QUEUE_PLAYLIST; /* §23:默认播放队列=歌单窗口 */
g_st.search_tab = 0;
appstate_srch_hist_init(); /* §23:读取本地搜索历史 */
return 0;
}
@@ -362,10 +370,11 @@ void appstate_adopt_songs(int n)
int appstate_load_playlist_songs_page(int idx, int offset, int limit)
{
if (idx < 0 || idx >= g_st.playlist_count) return NCM_ERR_ARG;
/* 搜索歌单详情(§23):idx==NCM_PL_SEARCH 时读 g_st.search_pl_open(复用本链路)。 */
if (idx != NCM_PL_SEARCH && (idx < 0 || idx >= g_st.playlist_count)) return NCM_ERR_ARG;
/* 不在此处写 g_st.active_playlist:调用方(playlist_open/UI)已在 UI 线程设定;
* 若后台 worker 在此改写,会覆盖用户已切换到的新歌单,造成“打开 B 却显示 A”。 */
const ncm_playlist_t *pl = &g_st.playlists[idx];
const ncm_playlist_t *pl = (idx == NCM_PL_SEARCH) ? &g_st.search_pl_open : &g_st.playlists[idx];
if (limit <= 0 || limit > NCM_SONG_BATCH) limit = NCM_SONG_BATCH;
if (offset < 0) offset = 0;
@@ -412,6 +421,10 @@ int appstate_load_playlist_songs(int idx)
void appstate_apply_playlist_cover(int idx)
{
if (idx == NCM_PL_SEARCH) {
ncm_strlcpy(g_st.cover_url, g_st.search_pl_open.cover, sizeof g_st.cover_url);
return;
}
if (idx < 0 || idx >= g_st.playlist_count) return;
ncm_strlcpy(g_st.cover_url, g_st.playlists[idx].cover, sizeof g_st.cover_url);
}
@@ -427,7 +440,9 @@ void appstate_set_playing_song(int idx, const char *cover_url)
int appstate_current_playing(void)
{
if (g_st.playing_song < 0 || g_st.playing_song >= g_st.track_ids_count) return -1;
if (g_st.playing_song < 0) return -1;
int cnt = appstate_queue_count(); /* §23:搜索队列时按搜索条数界(不误判歌单亮点) */
if (g_st.playing_song >= cnt) return -1;
return g_st.playing_song;
}
@@ -523,3 +538,214 @@ void autoplay_maybe(void)
if (pthread_create(&t, NULL, autoplay_worker, sel) == 0) pthread_detach(t);
else free(sel);
}
/* ============================================================
* 搜索(REPORT §23):独立结果数组(不覆写歌单窗口)+ 播放队列来源 + 本地历史。
* 结果数组读写经 g_search_mu;queue_src 决定超时/起播读数组还是窗口。
* ============================================================ */
void appstate_set_queue(int src) { g_st.queue_src = src; }
int appstate_get_queue(void) { return g_st.queue_src; }
int appstate_queue_count(void)
{
int c;
pthread_mutex_lock(&g_search_mu);
c = (g_st.queue_src == NCM_QUEUE_SEARCH) ? g_st.search_song_count : g_st.track_ids_count;
pthread_mutex_unlock(&g_search_mu);
return c < 0 ? 0 : c;
}
int appstate_queue_song(int idx, ncm_song_t *out)
{
if (!out) return NCM_ERR_ARG;
if (g_st.queue_src == NCM_QUEUE_SEARCH) {
pthread_mutex_lock(&g_search_mu);
int ok = (idx >= 0 && idx < g_st.search_song_count);
if (ok) *out = g_st.search_songs[idx];
pthread_mutex_unlock(&g_search_mu);
return ok ? NCM_OK : NCM_ERR_ARG;
}
if (appstate_song_copy(idx, out) == 0) return NCM_OK;
return appstate_song_fetch(idx, out);
}
int appstate_search_count(int tab)
{
int c;
pthread_mutex_lock(&g_search_mu);
c = (tab == 0) ? g_st.search_song_count : g_st.search_pl_count;
pthread_mutex_unlock(&g_search_mu);
return c;
}
int appstate_search_offset(int tab)
{
int o;
pthread_mutex_lock(&g_search_mu);
o = (tab == 0) ? g_st.search_song_offset : g_st.search_pl_offset;
pthread_mutex_unlock(&g_search_mu);
return o;
}
int appstate_search_has_more(int tab)
{
int m;
pthread_mutex_lock(&g_search_mu);
m = (tab == 0) ? g_st.search_song_more : g_st.search_pl_more;
pthread_mutex_unlock(&g_search_mu);
return m;
}
int appstate_search_song(int i, ncm_song_t *out)
{
if (!out) return NCM_ERR_ARG;
pthread_mutex_lock(&g_search_mu);
int ok = (i >= 0 && i < g_st.search_song_count);
if (ok) *out = g_st.search_songs[i];
pthread_mutex_unlock(&g_search_mu);
return ok ? NCM_OK : NCM_ERR_ARG;
}
int appstate_search_pl(int i, ncm_playlist_t *out)
{
if (!out) return NCM_ERR_ARG;
pthread_mutex_lock(&g_search_mu);
int ok = (i >= 0 && i < g_st.search_pl_count);
if (ok) *out = g_st.search_pls[i];
pthread_mutex_unlock(&g_search_mu);
return ok ? NCM_OK : NCM_ERR_ARG;
}
void appstate_search_clear(int tab)
{
pthread_mutex_lock(&g_search_mu);
if (tab == 0) {
g_st.search_song_count = 0; g_st.search_song_offset = 0;
g_st.search_song_total = -1; g_st.search_song_more = 1;
memset(g_st.search_songs, 0, sizeof g_st.search_songs);
} else {
g_st.search_pl_count = 0; g_st.search_pl_offset = 0;
g_st.search_pl_total = -1; g_st.search_pl_more = 1;
memset(g_st.search_pls, 0, sizeof g_st.search_pls);
}
pthread_mutex_unlock(&g_search_mu);
}
/* 追加单曲结果(worker 锁内):写 g_st.search_songs[count..),回填 offset/total/has_more。 */
void appstate_search_append_songs(int n, const ncm_song_t *s, int64_t total)
{
if (n <= 0 || !s) return;
pthread_mutex_lock(&g_search_mu);
int room = NCM_SEARCH_SONG_MAX - g_st.search_song_count;
if (n > room) n = room;
if (n > 0) memcpy(&g_st.search_songs[g_st.search_song_count], s, (size_t)n * sizeof(ncm_song_t));
g_st.search_song_count += n;
g_st.search_song_offset = g_st.search_song_count;
g_st.search_song_total = (total >= 0) ? (int)total : g_st.search_song_total;
int t = g_st.search_song_total;
g_st.search_song_more = (t >= 0) ? (g_st.search_song_count < t) : (n >= NCM_SEARCH_PAGE);
pthread_mutex_unlock(&g_search_mu);
}
void appstate_search_append_pls(int n, const ncm_playlist_t *p, int64_t total)
{
if (n <= 0 || !p) return;
pthread_mutex_lock(&g_search_mu);
int room = NCM_SEARCH_PL_MAX - g_st.search_pl_count;
if (n > room) n = room;
if (n > 0) memcpy(&g_st.search_pls[g_st.search_pl_count], p, (size_t)n * sizeof(ncm_playlist_t));
g_st.search_pl_count += n;
g_st.search_pl_offset = g_st.search_pl_count;
g_st.search_pl_total = (total >= 0) ? (int)total : g_st.search_pl_total;
int t = g_st.search_pl_total;
g_st.search_pl_more = (t >= 0) ? (g_st.search_pl_count < t) : (n >= NCM_SEARCH_PAGE);
pthread_mutex_unlock(&g_search_mu);
}
/* ---- 搜索历史(≤NCM_SEARCH_HIST_MAX,最新在前;单键 meow_config 持久化,条目以 \n 连接)---- */
#define NCM_SEARCH_HIST_KEY "ncm.search_history"
static char g_hist[NCM_SEARCH_HIST_MAX][NCM_SEARCH_HIST_LEN];
static int g_hist_count;
static int g_hist_loaded;
static void srch_hist_load(void)
{
char buf[2048];
if (meow_config_get(NCM_SEARCH_HIST_KEY, buf, sizeof buf) != MEOW_CFG_OK) { g_hist_count = 0; return; }
g_hist_count = 0;
char *p = buf;
while (p && *p && g_hist_count < NCM_SEARCH_HIST_MAX) {
char *nl = strchr(p, '\n');
size_t len = nl ? (size_t)(nl - p) : strlen(p);
if (len >= NCM_SEARCH_HIST_LEN) len = NCM_SEARCH_HIST_LEN - 1;
if (len > 0) { memcpy(g_hist[g_hist_count], p, len); g_hist[g_hist_count][len] = 0; g_hist_count++; }
if (!nl) break;
p = nl + 1;
}
}
static void srch_hist_save(void)
{
char buf[2048];
size_t bl = 0;
buf[0] = 0;
for (int i = 0; i < g_hist_count; i++) {
int n = snprintf(buf + bl, sizeof buf - bl, "%s%s", i ? "\n" : "", g_hist[i]);
if (n < 0) break;
bl += (size_t)n;
if (bl >= sizeof buf - 1) break;
}
meow_config_set(NCM_SEARCH_HIST_KEY, buf);
}
static void appstate_srch_hist_init(void)
{
if (!g_hist_loaded) { srch_hist_load(); g_hist_loaded = 1; }
}
int appstate_search_history_count(void)
{
appstate_srch_hist_init();
return g_hist_count;
}
int appstate_search_history_get(int i, char *out, size_t sz)
{
appstate_srch_hist_init();
if (i < 0 || i >= g_hist_count) return 0;
ncm_strlcpy(out, g_hist[i], sz);
return 1;
}
void appstate_search_history_add(const char *kw)
{
if (!kw || !kw[0]) return;
appstate_srch_hist_init();
char tmp[NCM_SEARCH_HIST_MAX][NCM_SEARCH_HIST_LEN];
int m = 0;
ncm_strlcpy(tmp[m], kw, sizeof tmp[m]);
m++;
for (int i = 0; i < g_hist_count && m < NCM_SEARCH_HIST_MAX; i++)
if (strcmp(g_hist[i], kw) != 0) { ncm_strlcpy(tmp[m], g_hist[i], sizeof tmp[m]); m++; }
g_hist_count = m;
memcpy(g_hist, tmp, sizeof g_hist);
srch_hist_save();
}
void appstate_search_history_remove(int i)
{
appstate_srch_hist_init();
if (i < 0 || i >= g_hist_count) return;
for (int j = i; j < g_hist_count - 1; j++)
ncm_strlcpy(g_hist[j], g_hist[j + 1], sizeof g_hist[j]);
g_hist_count--;
srch_hist_save();
}
void appstate_search_history_clear(void)
{
g_hist_count = 0;
g_hist_loaded = 1;
srch_hist_save();
}