ncm 三期三批: 歌曲对象瘦身(1324→608B)+滑动窗口(NCM_SONG_WINDOW=256, 取消1000截断支撑任意长度歌单)+播放快照解耦 — agent-251
- core/ncm_api.h: ncm_song_t 瘦身 id24/name128/art96/al96/pic256/dt int32 (1324B→608B) - appstate.c 滑动窗口: song_ids 全量 trackIds 索引(16384) + songs[256] 窗口感知 (song_valid 位图/window_base/滑窗 memmove 保留重叠), ensure_range 批网格(100)对齐 预取, songmeta.c 盘缓存 <cache_dir>/meta/<id>.bin (回滚/二次进入 0 网络请求) - playlist.c 触底分页退役→视口 ensure(±24 行), 占位行可直点(播放线程 fetch 物化), footer 改视口区间/总数 - 播放兼容: g_play.play_song 快照(mu), run_song 起播物化后全链路(下载/歌词/IPC META/ 播放页 UI)读快照——窗口滑出当前播放曲不崩; 边界 song_count→track_ids_count - 验证(0 真实请求): pagination_sim 全 PASS(5191 无截断/52 批); window_sim 6000 首 全程下滚 750 步视口全物化/song_detail 恰 60 次/回滚+0 请求/播放 fetch 不滑窗; core 宿主+qemu 全 PASS; mipsel -Wall -Wextra 0 warning, bss 2052068→1080900(-47%) - 未部署真机(设备音频线排查中, 部署留主会话)
This commit is contained in:
1 parent
d8898afa09
commit
997f89f879
18 files changed
+987
-209
No files matched your search
@@ -7,6 +7,9 @@
|
||||
|
||||
#define NCM_DEFAULT_DATA "/usr/data/mnt/sd_0/MeowData/dev.player.ncm/data"
|
||||
|
||||
/* 滑动窗口互斥(REPORT §17):窗口读写(滑窗/物化/拷贝)全走此锁,见下方窗口节注释。 */
|
||||
static pthread_mutex_t g_song_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
int appstate_init(void)
|
||||
{
|
||||
const char *dd = getenv("MEOW_DATA_DIR");
|
||||
@@ -16,7 +19,7 @@ int appstate_init(void)
|
||||
|
||||
ncm_ctx_init(&g_st.ctx, g_st.data_dir); /* device_id + cookie 载入 + cache */
|
||||
if (g_st.ctx.cache.dir[0])
|
||||
strncpy(g_st.cache_dir, g_st.ctx.cache.dir, sizeof g_st.cache_dir - 1);
|
||||
ncm_strlcpy(g_st.cache_dir, g_st.ctx.cache.dir, sizeof g_st.cache_dir);
|
||||
else
|
||||
ncm_ctx_default_cache_dir(g_st.data_dir, g_st.cache_dir, sizeof g_st.cache_dir);
|
||||
|
||||
@@ -80,7 +83,12 @@ void appstate_logout(void)
|
||||
g_st.nickname[0] = 0;
|
||||
g_st.active_playlist = -1;
|
||||
g_st.playing_song = -1;
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
memset(g_st.song_valid, 0, sizeof g_st.song_valid);
|
||||
g_st.window_base = 0;
|
||||
g_st.song_count = 0;
|
||||
g_st.track_ids_count = 0;
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
meow_config_remove("ncm.uid");
|
||||
meow_config_remove("ncm.nickname");
|
||||
cover_cache_reset();
|
||||
@@ -120,6 +128,219 @@ int appstate_load_playlists(void)
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 滑动窗口(REPORT §17,三期第三批)
|
||||
* songs[i] ↔ 全局下标 window_base+i;song_valid[] 为槽位有效位图。
|
||||
* 写侧(滑窗/物化)与读侧(song_copy)全走 g_song_mu;网络/盘 IO 在锁外做,
|
||||
* 锁内只做内存搬移,UI 行回调(song_copy)不被网络阻塞。
|
||||
* 单写者假设:ensure/page 仅由 playlist worker(及兼容入口)驱动,同一时刻最多一个。
|
||||
* ============================================================ */
|
||||
|
||||
static int slot_valid_locked(int gidx)
|
||||
{
|
||||
int s = gidx - g_st.window_base;
|
||||
return s >= 0 && s < NCM_SONG_WINDOW && g_st.song_valid[s];
|
||||
}
|
||||
|
||||
static void win_recount_locked(void)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < NCM_SONG_WINDOW; i++)
|
||||
if (g_st.song_valid[i]) n++;
|
||||
g_st.song_count = n;
|
||||
}
|
||||
|
||||
/* 锁内滑窗:重叠区 songs+valid 一起 memmove 保留,非重叠端清零(出窗槽位失效)。
|
||||
* 出窗歌曲的元数据此前已落盘(songmeta),丢弃无数据损失。 */
|
||||
static void win_slide_locked(int new_base)
|
||||
{
|
||||
int old_base = g_st.window_base;
|
||||
if (new_base == old_base) return;
|
||||
int ov_lo = (old_base > new_base) ? old_base : new_base;
|
||||
int ov_hi = (old_base + NCM_SONG_WINDOW < new_base + NCM_SONG_WINDOW)
|
||||
? old_base + NCM_SONG_WINDOW : new_base + NCM_SONG_WINDOW;
|
||||
if (ov_lo < ov_hi) {
|
||||
int src = ov_lo - old_base, dst = ov_lo - new_base, n = ov_hi - ov_lo;
|
||||
memmove(&g_st.songs[dst], &g_st.songs[src], (size_t)n * sizeof(ncm_song_t));
|
||||
memmove(&g_st.song_valid[dst], &g_st.song_valid[src], (size_t)n);
|
||||
if (dst > 0) memset(g_st.song_valid, 0, (size_t)dst);
|
||||
if (dst + n < NCM_SONG_WINDOW)
|
||||
memset(&g_st.song_valid[dst + n], 0, (size_t)(NCM_SONG_WINDOW - dst - n));
|
||||
} else {
|
||||
memset(g_st.song_valid, 0, sizeof g_st.song_valid);
|
||||
}
|
||||
g_st.window_base = new_base;
|
||||
win_recount_locked();
|
||||
}
|
||||
|
||||
int appstate_range_loaded(int lo, int hi)
|
||||
{
|
||||
if (lo < 0) lo = 0;
|
||||
if (hi > g_st.track_ids_count) hi = g_st.track_ids_count;
|
||||
if (lo >= hi) return 1;
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int ok = 1;
|
||||
for (int i = lo; i < hi; i++)
|
||||
if (!slot_valid_locked(i)) { ok = 0; break; }
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
return ok;
|
||||
}
|
||||
|
||||
int appstate_song_copy(int gidx, ncm_song_t *out)
|
||||
{
|
||||
if (!out) return -1;
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int s = gidx - g_st.window_base;
|
||||
int ok = (s >= 0 && s < NCM_SONG_WINDOW && g_st.song_valid[s]);
|
||||
if (ok) *out = g_st.songs[s];
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
int appstate_song_ensure_range(int lo, int hi)
|
||||
{
|
||||
if (lo < 0) lo = 0;
|
||||
if (hi > g_st.track_ids_count) hi = g_st.track_ids_count;
|
||||
if (lo >= hi) return NCM_OK;
|
||||
|
||||
/* 目标基址:请求区间居中(前后留余量,滚动反向时不立即滑窗),钳制到合法范围。 */
|
||||
int base;
|
||||
if (hi - lo <= NCM_SONG_WINDOW) {
|
||||
base = (lo + hi) / 2 - NCM_SONG_WINDOW / 2;
|
||||
if (base > lo) base = lo;
|
||||
if (base + NCM_SONG_WINDOW < hi) base = hi - NCM_SONG_WINDOW;
|
||||
int maxb = g_st.track_ids_count - NCM_SONG_WINDOW;
|
||||
if (maxb < 0) maxb = 0;
|
||||
if (base > maxb) base = maxb;
|
||||
if (base < 0) base = 0;
|
||||
} else {
|
||||
base = lo; /* 请求区间超窗(不应发生):覆盖前缀 */
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int covered = 1;
|
||||
for (int i = lo; i < hi; i++)
|
||||
if (!slot_valid_locked(i)) { covered = 0; break; }
|
||||
if (!covered && base != g_st.window_base) win_slide_locked(base);
|
||||
else base = g_st.window_base; /* 已覆盖:维持原基址 */
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
if (covered) return NCM_OK;
|
||||
|
||||
/* 逐段补齐 [lo,hi) 内 invalid 槽:先试盘缓存(songmeta),miss 攒批 ≤NCM_SONG_BATCH
|
||||
* 走 song_detail;成功写窗 + 落盘。网络/盘 IO 均在锁外,锁内仅写槽。 */
|
||||
static ncm_song_t s_fetch[NCM_SONG_BATCH]; /* 单写者(playlist worker),免 60KB 栈 */
|
||||
int rc = NCM_OK;
|
||||
for (int a = lo; a < hi && rc == NCM_OK; ) {
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int va = slot_valid_locked(a);
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
if (va) { a++; continue; }
|
||||
int b = a + 1;
|
||||
while (b < hi && b - a < NCM_SONG_BATCH) {
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int vb = slot_valid_locked(b);
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
if (vb) break;
|
||||
b++;
|
||||
}
|
||||
/* 批网格对齐预取:把 [a,b) 扩到 song_detail 批边界(§17.4——逐行滚动时若按原始
|
||||
* 小区间拉取,song_detail 调用次数会放大到 ~歌单长度/滚动步长;对齐到 100 首
|
||||
* 网格后每次补齐整批,总请求数 ≈ 歌单长度/NCM_SONG_BATCH)。 */
|
||||
int a0 = (a / NCM_SONG_BATCH) * NCM_SONG_BATCH;
|
||||
int b0 = ((b + NCM_SONG_BATCH - 1) / NCM_SONG_BATCH) * NCM_SONG_BATCH;
|
||||
int wend2 = g_st.window_base + NCM_SONG_WINDOW;
|
||||
if (wend2 > g_st.track_ids_count) wend2 = g_st.track_ids_count;
|
||||
if (a0 < g_st.window_base) a0 = g_st.window_base;
|
||||
if (b0 > wend2) b0 = wend2;
|
||||
if (b0 - a0 > NCM_SONG_BATCH) b0 = a0 + NCM_SONG_BATCH;
|
||||
/* 本段 [a0,b0):盘命中直接写窗,有效槽跳过,miss 记录待批拉 */
|
||||
int64_t ids[NCM_SONG_BATCH];
|
||||
int gslot[NCM_SONG_BATCH];
|
||||
int m = 0;
|
||||
for (int i = a0; i < b0; i++) {
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int vv = slot_valid_locked(i);
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
if (vv) continue;
|
||||
char sid[24];
|
||||
snprintf(sid, sizeof sid, "%lld", (long long)g_st.song_ids[i]);
|
||||
ncm_song_t ms;
|
||||
if (songmeta_load(g_st.cache_dir, sid, &ms) == NCM_OK) {
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int s = i - g_st.window_base;
|
||||
if (s >= 0 && s < NCM_SONG_WINDOW) {
|
||||
g_st.songs[s] = ms;
|
||||
g_st.song_valid[s] = 1;
|
||||
}
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
} else {
|
||||
ids[m] = g_st.song_ids[i];
|
||||
gslot[m] = i;
|
||||
m++;
|
||||
}
|
||||
}
|
||||
if (m > 0) {
|
||||
int bn = 0;
|
||||
int r2 = ncm_api_song_detail(g_st.ctx.device_id, g_st.ctx.cookie,
|
||||
ids, m, s_fetch, NCM_SONG_BATCH, &bn);
|
||||
if (r2 != NCM_OK) { rc = r2; break; }
|
||||
if (bn > m) bn = m;
|
||||
for (int k = 0; k < bn; k++) { /* song_detail 结果与请求 ids 同序 */
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
int s = gslot[k] - g_st.window_base;
|
||||
if (s >= 0 && s < NCM_SONG_WINDOW) {
|
||||
g_st.songs[s] = s_fetch[k];
|
||||
g_st.song_valid[s] = 1;
|
||||
}
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
char sid[24];
|
||||
snprintf(sid, sizeof sid, "%lld", (long long)ids[k]);
|
||||
songmeta_save(g_st.cache_dir, sid, &s_fetch[k]);
|
||||
}
|
||||
if (bn < m) rc = NCM_ERR_JSON; /* 部分返回:缺槽留 invalid,下次 ensure 再试 */
|
||||
}
|
||||
a = b;
|
||||
}
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
win_recount_locked();
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int appstate_song_fetch(int gidx, ncm_song_t *out)
|
||||
{
|
||||
if (!out) return NCM_ERR_ARG;
|
||||
if (appstate_song_copy(gidx, out) == 0) return NCM_OK; /* 窗内直接命中 */
|
||||
if (gidx < 0 || gidx >= g_st.track_ids_count) return NCM_ERR_ARG;
|
||||
int64_t id = g_st.song_ids[gidx];
|
||||
if (id == 0) return NCM_ERR_ARG; /* 无索引(如搜索上下文窗外) */
|
||||
char sid[24];
|
||||
snprintf(sid, sizeof sid, "%lld", (long long)id);
|
||||
if (songmeta_load(g_st.cache_dir, sid, out) == NCM_OK) return NCM_OK; /* 盘缓存 */
|
||||
int bn = 0;
|
||||
int rc = ncm_api_song_detail(g_st.ctx.device_id, g_st.ctx.cookie, &id, 1, out, 1, &bn);
|
||||
if (rc != NCM_OK) return rc;
|
||||
if (bn < 1) return NCM_ERR_NOTFOUND;
|
||||
songmeta_save(g_st.cache_dir, sid, out);
|
||||
return NCM_OK;
|
||||
}
|
||||
|
||||
void appstate_adopt_songs(int n)
|
||||
{
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
if (n < 0) n = 0;
|
||||
if (n > NCM_SONG_WINDOW) n = NCM_SONG_WINDOW;
|
||||
memset(g_st.song_valid, 0, sizeof g_st.song_valid);
|
||||
for (int i = 0; i < n; i++) {
|
||||
g_st.song_valid[i] = 1;
|
||||
g_st.song_ids[i] = strtoll(g_st.songs[i].id, NULL, 10);
|
||||
}
|
||||
g_st.window_base = 0;
|
||||
g_st.song_count = n;
|
||||
g_st.track_ids_count = n;
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
}
|
||||
|
||||
int appstate_load_playlist_songs_page(int idx, int offset, int limit)
|
||||
{
|
||||
if (idx < 0 || idx >= g_st.playlist_count) return NCM_ERR_ARG;
|
||||
@@ -130,61 +351,44 @@ int appstate_load_playlist_songs_page(int idx, int offset, int limit)
|
||||
if (offset < 0) offset = 0;
|
||||
|
||||
/* REPORT §13.1:/api/playlist/track/all 服务端分页端点真机全 404(NCM 无此路由),弃用。
|
||||
* 回到 §10.11 已验证路径并做**真分页**:detail(n=1)取【全量】trackIds 入 song_ids[],
|
||||
* song_detail 按 ≤NCM_SONG_BATCH 分批拉歌曲对象(含 al.picUrl),offset 为「下一页」起始;
|
||||
* 列表触底驱动 offset 前进,song_count 逐页累加(不再一次性拉满,大歌单不冻 UI,
|
||||
* 也不把「第 N 页之后的歌」挡在 256 之外——NCM_SONG_MAX 上限提升到 1000,见 §14)。 */
|
||||
* §14 的分页(append-only 到 NCM_SONG_MAX=1000 截断)在 §17 升级为滑动窗口:
|
||||
* detail(n=1)仍取【全量】trackIds 入 song_ids[](≤ NCM_TRACKID_MAX,支撑 6000+ 首),
|
||||
* 歌曲对象只物化视口附近的 NCM_SONG_WINDOW 首(offset 为「要确保的区间」起点)。 */
|
||||
|
||||
if (offset == 0) {
|
||||
/* 重头:重建 trackIds 索引 + 清空已物化歌曲(song_count 无论成败都归零)。 */
|
||||
/* 重头:清窗口 + 重建 trackIds 索引(song_count/track_ids_count 无论成败都归零)。 */
|
||||
pthread_mutex_lock(&g_song_mu);
|
||||
memset(g_st.song_valid, 0, sizeof g_st.song_valid);
|
||||
memset(g_st.song_ids, 0, sizeof g_st.song_ids);
|
||||
memset(g_st.songs, 0, sizeof g_st.songs);
|
||||
g_st.window_base = 0;
|
||||
g_st.song_count = 0;
|
||||
g_st.track_ids_count = 0;
|
||||
pthread_mutex_unlock(&g_song_mu);
|
||||
int idn = 0;
|
||||
int rc = ncm_api_playlist_detail(g_st.ctx.device_id, g_st.ctx.cookie, pl->id,
|
||||
NULL, 0, NULL, 0,
|
||||
g_st.song_ids, NCM_SONG_MAX, &idn);
|
||||
g_st.song_ids, NCM_TRACKID_MAX, &idn);
|
||||
if (rc != NCM_OK) return rc;
|
||||
g_st.track_ids_count = idn;
|
||||
/* 首屏只需第 0 页:song_detail 只拉前 limit 首,其余待滚动触底再拉(分页驱动)。 */
|
||||
appstate_apply_playlist_cover(idx);
|
||||
} else if (g_st.track_ids_count == 0) {
|
||||
/* 防御:offset>0 但没有索引(不应发生;主路径先走 offset=0)。 */
|
||||
return NCM_ERR_ARG;
|
||||
}
|
||||
|
||||
if (offset >= g_st.track_ids_count) return NCM_OK; /* 已到尾部/上限 */
|
||||
if (offset >= NCM_SONG_MAX) return NCM_OK; /* 内存上限:不再物化更多 */
|
||||
|
||||
int can = g_st.track_ids_count - offset;
|
||||
int room = NCM_SONG_MAX - offset;
|
||||
if (can > limit) can = limit; /* 每页 ≤ limit(保守 100) */
|
||||
if (can > room) can = room; /* 不越界 song_ids[]/songs[] */
|
||||
|
||||
int bn = 0;
|
||||
int rc = ncm_api_song_detail(g_st.ctx.device_id, g_st.ctx.cookie, &g_st.song_ids[offset],
|
||||
can, &g_st.songs[offset], NCM_SONG_MAX - offset, &bn);
|
||||
if (rc != NCM_OK) return rc; /* 失败:song_count 不变(保留已载页) */
|
||||
|
||||
g_st.song_count = offset + bn; /* 成功:累加本页 */
|
||||
appstate_apply_playlist_cover(idx);
|
||||
return NCM_OK;
|
||||
if (offset >= g_st.track_ids_count) return NCM_OK; /* 已到尾部 */
|
||||
int hi = offset + limit;
|
||||
if (hi > g_st.track_ids_count) hi = g_st.track_ids_count;
|
||||
return appstate_song_ensure_range(offset, hi);
|
||||
}
|
||||
|
||||
int appstate_load_playlist_songs(int idx)
|
||||
{
|
||||
/* 兼容入口:整批载入(内部按页循环到 NCM_SONG_MAX 或歌单尾部)。 */
|
||||
/* 兼容入口:载索引 + 物化首个窗口。 */
|
||||
int rc = appstate_load_playlist_songs_page(idx, 0, NCM_SONG_BATCH);
|
||||
if (rc != NCM_OK) return rc;
|
||||
int off = g_st.song_count;
|
||||
while (off < g_st.track_ids_count && off < NCM_SONG_MAX) {
|
||||
rc = appstate_load_playlist_songs_page(idx, off, NCM_SONG_BATCH);
|
||||
if (rc != NCM_OK) return rc;
|
||||
int nxt = g_st.song_count;
|
||||
if (nxt <= off) break; /* 无进展:防死循环 */
|
||||
off = nxt;
|
||||
}
|
||||
return NCM_OK;
|
||||
int hi = (g_st.track_ids_count < NCM_SONG_WINDOW) ? g_st.track_ids_count : NCM_SONG_WINDOW;
|
||||
return appstate_song_ensure_range(0, hi);
|
||||
}
|
||||
|
||||
void appstate_apply_playlist_cover(int idx)
|
||||
@@ -203,7 +407,7 @@ 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.song_count) return -1;
|
||||
if (g_st.playing_song < 0 || g_st.playing_song >= g_st.track_ids_count) return -1;
|
||||
return g_st.playing_song;
|
||||
}
|
||||
|
||||
@@ -227,18 +431,20 @@ static int autoplay_try_playlist(int pl)
|
||||
return 0;
|
||||
}
|
||||
char cpath[600];
|
||||
for (int i = 0; i < g_st.song_count; i++) {
|
||||
if (ncm_cache_lookup(&g_st.ctx.cache, g_st.songs[i].id, ".mp3",
|
||||
for (int i = 0; i < g_st.track_ids_count; i++) { /* 滑窗模型:锁内拷贝窗内有效槽 */
|
||||
ncm_song_t s;
|
||||
if (appstate_song_copy(i, &s) != 0) break; /* 出窗即停(首页之后未物化) */
|
||||
if (ncm_cache_lookup(&g_st.ctx.cache, s.id, ".mp3",
|
||||
cpath, sizeof cpath) == NCM_OK) {
|
||||
g_st.active_playlist = pl;
|
||||
meow_log(MEOW_LOG_INFO, "autoplay: playlist[%d] song[%d] id=%s cached -> play",
|
||||
pl, i, g_st.songs[i].id);
|
||||
pl, i, s.id);
|
||||
player_manager_play(i);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
meow_log(MEOW_LOG_INFO, "autoplay: playlist[%d] first page %d songs, no cached track",
|
||||
pl, g_st.song_count);
|
||||
meow_log(MEOW_LOG_INFO, "autoplay: playlist[%d] first window %d/%d songs, no cached track",
|
||||
pl, g_st.song_count, g_st.track_ids_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/* logic/songmeta.c — 歌曲元数据本地缓存(REPORT §17 滑动窗口配套)。
|
||||
*
|
||||
* 拉取成功的 song_detail 结果逐首序列化到 <cache_dir>/meta/<id>.bin;滑动窗口补载时
|
||||
* 先读盘(命中免 song_detail 请求),离线/二次进入歌单滚动全程零网络。
|
||||
*
|
||||
* 取舍(刻意从简):
|
||||
* - 不进 ncm_cache LRU 池:meta 每首 ≈600B,一万首 ≈6MB,不参与 10GB 级 LRU 淘汰,
|
||||
* 也不被 ncm_cache_clear(只删 song_* 顶层文件)误清——清音频缓存不丢浏览元数据。
|
||||
* - 格式 = 头(magic/ver/size) + ncm_song_t 原始内存镜像:同机同构读写,跨架构/结构体
|
||||
* 改版由 ver+size 失配判 miss(缓存可再生,最坏结果 = 回退 song_detail 网络拉取)。
|
||||
*/
|
||||
#include "../ncm_app_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define SONGMETA_MAGIC 0x4D4D434Eu /* "NCMM" */
|
||||
#define SONGMETA_VER 1
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint16_t ver;
|
||||
uint16_t size; /* sizeof(ncm_song_t),失配判 miss */
|
||||
} songmeta_hdr_t;
|
||||
|
||||
static int songmeta_path(const char *dir, const char *id, char *out, size_t sz)
|
||||
{
|
||||
if (!dir || !dir[0] || !id || !id[0]) return -1;
|
||||
int n = snprintf(out, sz, "%s/meta/%s.bin", dir, id);
|
||||
return (n > 0 && (size_t)n < sz) ? 0 : -1;
|
||||
}
|
||||
|
||||
int songmeta_load(const char *cache_dir, const char *song_id, ncm_song_t *out)
|
||||
{
|
||||
char path[NCM_CACHE_DIR_MAX + 48];
|
||||
if (songmeta_path(cache_dir, song_id, path, sizeof path) != 0) return NCM_ERR_NOENT;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return NCM_ERR_NOENT;
|
||||
songmeta_hdr_t h;
|
||||
int ok = (fread(&h, 1, sizeof h, f) == sizeof h) &&
|
||||
h.magic == SONGMETA_MAGIC && h.ver == SONGMETA_VER &&
|
||||
h.size == sizeof(ncm_song_t) &&
|
||||
(fread(out, 1, sizeof(ncm_song_t), f) == sizeof(ncm_song_t));
|
||||
fclose(f);
|
||||
return ok ? NCM_OK : NCM_ERR_NOENT;
|
||||
}
|
||||
|
||||
int songmeta_save(const char *cache_dir, const char *song_id, const ncm_song_t *s)
|
||||
{
|
||||
if (!cache_dir || !cache_dir[0] || !song_id || !song_id[0] || !s) return NCM_ERR_ARG;
|
||||
char dir[NCM_CACHE_DIR_MAX + 16];
|
||||
int n = snprintf(dir, sizeof dir, "%s/meta", cache_dir);
|
||||
if (n <= 0 || (size_t)n >= sizeof dir) return NCM_ERR_ARG;
|
||||
mkdir(cache_dir, 0755); /* 幂等(缓存目录通常已由 ncm_cache_init 建好) */
|
||||
mkdir(dir, 0755);
|
||||
|
||||
char path[NCM_CACHE_DIR_MAX + 48];
|
||||
if (songmeta_path(cache_dir, song_id, path, sizeof path) != 0) return NCM_ERR_ARG;
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return NCM_ERR_IO;
|
||||
songmeta_hdr_t h = { SONGMETA_MAGIC, SONGMETA_VER, (uint16_t)sizeof(ncm_song_t) };
|
||||
int ok = (fwrite(&h, 1, sizeof h, f) == sizeof h) &&
|
||||
(fwrite(s, 1, sizeof(ncm_song_t), f) == sizeof(ncm_song_t));
|
||||
fclose(f);
|
||||
if (!ok) remove(path); /* 半截文件不留(下次判 miss 重拉) */
|
||||
return ok ? NCM_OK : NCM_ERR_IO;
|
||||
}
|
||||
Reference in new issue
Block a user