Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/logic/cover.c
T
MeowOS f23720f934 feat(ncm): 三期第二批 — 中文搜索接拼音IME+设置页做好(音质全档/缓存上限可设数字+清理缓存/关于)+缓存LRU淘汰接生产路径(写前写后+手动调低上限)
- 搜索页输入框 MEOW_IME_ASCII→MEOW_IME_PINYIN: 候选上屏UTF-8经 on_change 实时回写 g_st.search_query, 键盘「拼/英」「中」切换, 英文零回归; 搜索仍仅在点「搜索」时发 cloudsearch(风控)
- 设置页 5 行: 音质(Hi-Res 默认,Lossless/Exhigh/Higher/Standard 全档,core 既有降级链)/缓存上限(1~100GB 预设+「自定义」NUM IME 数字输入,默认10上限100)/清理缓存(显示当前占用,ncm_cache_clear)/退出登录/关于行
- 缓存淘汰接线: ncm_cache_evict 进生产路径 — player_manager(下载写前/写后)+cover(写前/写后)+appstate_save_settings(手动调低上限立即淘汰); 封面与 mp3 同池; 上限读设置值
- core 新增 ncm_cache_clear; 设置持久化走 meow_config_* → MeowData/dev.player.ncm/data
- bench/cache_evict_sim.c 离线回归(0网络) 6 组 PASS; pagination_sim/core(host+mipsel/qemu)/6页宿主渲染 全过; mipsel -Wall -Wextra 0 warning
(未动 sdk/、meow_launcherd/、vendor/;0 真实 NCM 请求;真机部署留用户)
2026-08-31 20:15:31 +08:00

190 lines
7.3 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.
/* logic/cover.c — 封面下载(HTTPS)+ 限幅 + 解码缓存(单张,URL 变更即换;后台线程)。
*
* 内存纪律:下载限幅 NCM_COVER_MAX(~288KB);解码后的 RGBA 保持单张(换封面先 free 旧再解码)。
* 线程模型:一个持久的 cover 工作线程负责「目标 URL → 下载 → 解码 → 换入」;UI 线程只经
* cover_request(url)(提交目标)与 cover_get()/cover_changed()(读取/判变)+ cover_ack()(消费)。
* 工作线程不碰 meow_ui(绘制在 UI 线程 tick 判 cover_changed 后触发 invalidate)。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
static pthread_mutex_t g_cm = PTHREAD_MUTEX_INITIALIZER;
static meow_img *g_cover_img;
static char g_cover_url[512]; /* g_cover_img 对应的 URL */
static char g_tgt_url[512]; /* 目标 URL(worker 拉取) */
static pthread_t g_thr;
static int g_thr_on;
static int g_quit;
static volatile int g_gen; /* 每次换入 +1 */
static int g_acked; /* UI 已消费的版本 */
static char g_cover_path[512]; /* 封面原始文件路径(供 launcher) */
static unsigned char g_buf[NCM_COVER_MAX]; /* 单张下载缓冲(常驻) */
static meow_img *decode_cover(const char *url, size_t *out_len)
{
if (!url || !url[0]) return NULL;
ncm_http_req_t req;
memset(&req, 0, sizeof req);
req.url = url;
req.method = "GET";
req.extra_hdrs = "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
req.verify_ca = NCM_HTTP_VERIFY_DEFAULT;
req.timeout_ms = 10000;
ncm_http_resp_t resp;
int rc = ncm_http_request(&req, (char *)g_buf, sizeof g_buf, &resp);
if (rc != NCM_OK || resp.status != 200 || resp.body_len == 0) return NULL;
if (out_len) *out_len = resp.body_len;
return meow_img_load_mem((const void *)resp.body, (int)resp.body_len);
}
/* 封面缓存键:URL 的 djb2 哈希(稳定性由 URL 决定——同一专辑封面 URL 名即可命中)。
* 走 ncm_cache(LRU 口径沿用 ncm_cache,见 REPORT §14.2):键进 ncm_cache_path => 文件
* `song_cov<hex>.jpg`,与 mp3(song_<id>.mp3)同池,lookup/commit 刷新 mtime(LRU)。 */
static void cover_cache_id(const char *url, char *out, size_t out_sz)
{
unsigned long h = 5381;
for (const char *p = url; *p; p++) h = ((h << 5) + h) + (unsigned char)*p;
snprintf(out, out_sz, "cov%lx", h);
}
/* 读整文件到 buf(< cap),成功返回 1,写入 *len。 */
static int read_file_into_buf(const char *path, unsigned char *buf, size_t cap, size_t *len)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
size_t n = fread(buf, 1, cap, f);
fclose(f);
if (n == 0) return 0;
if (len) *len = n;
return 1;
}
/* 写 cover_current.jpg 供 launcher 媒体卡片读(兼容保留)。调用方须已持有 g_cm
* (早前版本在此内再 lock g_cm → 非递归锁自锁死——cover worker 永久卡死、封面永不出现)。 */
static void save_cover_raw(const char *url, size_t len)
{
if (!url || len == 0) return;
if (g_st.cache_dir[0]) {
char path[NCM_CACHE_DIR_MAX + 32]; /* >512:宿主 glibc fortify 对 %s 展开宽证明可放 */
snprintf(path, sizeof path, "%s/cover_current.jpg", g_st.cache_dir);
ncm_store_write_file(path, g_buf, len);
ncm_strlcpy(g_cover_path, path, sizeof g_cover_path);
}
}
static void *cover_worker(void *arg)
{
(void)arg;
while (!g_quit) {
pthread_mutex_lock(&g_cm);
if (!g_tgt_url[0]) { pthread_mutex_unlock(&g_cm); usleep(250 * 1000); continue; }
char url[512];
ncm_strlcpy(url, g_tgt_url, sizeof url);
pthread_mutex_unlock(&g_cm);
char key[32];
cover_cache_id(url, key, sizeof key);
size_t blen = 0;
meow_img *img = NULL;
/* ① 缓存命中直接解码(免下载、离线可看;LRU:lookup 刷新 mtime) */
char cpath[512];
if (g_st.ctx.cache.dir[0] && ncm_cache_lookup(&g_st.ctx.cache, key, ".jpg",
cpath, sizeof cpath) == NCM_OK &&
read_file_into_buf(cpath, g_buf, sizeof g_buf, &blen)) {
img = meow_img_load_mem(g_buf, (int)blen);
}
/* ② 未命中 → 下载解码;成功后落进 ncm_cache(LRU 池,超限由 ncm_cache_evict 淘汰) */
if (!img) {
blen = 0;
img = decode_cover(url, &blen);
if (!img) { usleep(500 * 1000); continue; }
if (g_st.ctx.cache.dir[0]) {
/* 写前检查:池大小超限先按 LRU 淘汰(与 mp3 同池;封面约 200KB,先腾空间) */
ncm_cache_evict(&g_st.ctx.cache);
ncm_cache_writer_t *w = NULL;
if (ncm_cache_open(&g_st.ctx.cache, key, ".jpg", &w) == NCM_OK) {
ncm_cache_write(w, g_buf, blen);
ncm_cache_commit(w);
/* 写后检查:新封面可能把池推过上限 → 再按 LRU 淘汰最旧 */
ncm_cache_evict(&g_st.ctx.cache);
}
}
}
pthread_mutex_lock(&g_cm);
/* 目标仍是 url 才换入(期间未提交更早) */
if (g_tgt_url[0] && strcmp(g_tgt_url, url) == 0) {
if (g_cover_img) meow_img_free(g_cover_img);
g_cover_img = img;
ncm_strlcpy(g_cover_url, url, sizeof g_cover_url);
save_cover_raw(url, blen);
g_gen++;
} else {
meow_img_free(img);
}
pthread_mutex_unlock(&g_cm);
}
return NULL;
}
/* UI 线程:提交目标封面 URL(无网络/无 URL 则忽略)。 */
void cover_request(const char *url)
{
if (!url || !url[0]) return;
pthread_mutex_lock(&g_cm);
if (g_cover_url[0] && strcmp(g_cover_url, url) == 0) { pthread_mutex_unlock(&g_cm); return; }
ncm_strlcpy(g_tgt_url, url, sizeof g_tgt_url);
pthread_mutex_unlock(&g_cm);
if (!g_thr_on) {
g_thr_on = 1;
if (pthread_create(&g_thr, NULL, cover_worker, NULL) == 0)
pthread_detach(g_thr);
else
g_thr_on = 0;
}
}
/* UI 线程:读取当前封面(不拥有)。 */
meow_img *cover_get(void)
{
meow_img *im;
pthread_mutex_lock(&g_cm);
im = g_cover_img;
pthread_mutex_unlock(&g_cm);
return im;
}
/* 是否有新封面待绘制(UI tick 判变)。 */
int cover_changed(void) { return g_gen != g_acked; }
/* UI 消费一次:标记已见当前版本。 */
void cover_ack(void) { g_acked = g_gen; }
/* 封面原始文件路径(供 launcher 媒体卡片读);NULL=无。 */
const char *cover_current_path(void)
{
const char *p;
pthread_mutex_lock(&g_cm);
p = g_cover_path[0] ? g_cover_path : NULL;
pthread_mutex_unlock(&g_cm);
return p;
}
void cover_cache_reset(void)
{
pthread_mutex_lock(&g_cm);
if (g_cover_img) { meow_img_free(g_cover_img); g_cover_img = NULL; }
g_cover_url[0] = 0;
g_tgt_url[0] = 0;
g_cover_path[0] = 0;
pthread_mutex_unlock(&g_cm);
g_gen++;
g_acked = g_gen;
}