feat(ncm): 缓存分目录(songs//covers/)+ 陈旧 mp3 缓存修复

任务1 缓存分目录(用户定案):\n- ncm_cache_init mkdir songs/ 与 covers/ 子目录 + 一次性迁移\n  (根混放 song_*/cov*/cover_current.jpg 挪进两子目录,幂等,legacy_migrated 计数)\n- ncm_cache_path 按 id 前缀路由(cov*→covers/,其余→songs/)\n- LRU(evict/total_bytes/7天 .part 回收)只扫 songs/;clear 清 songs/+covers/ 的 song_*\n- cover.c save_cover_raw 落 covers/cover_current.jpg\n\n任务2 陈旧 mp3 缓存修复(真机实锤:hires 档键下存 mp3,播放命中旧档):\n- song_local_file 缓存命中且用户档>EXHIGH(无损+)且实体是 .mp3 = 静默降级产物\n  → 迁移重命名到 exhigh 键 song_<id>_exhigh.mp3(选极高仍可用;目标已存在删旧)\n  → 本档视为未命中走下载(311 flac 门禁拿真无损/正确降级)\n\n验证:cache_evict_sim case10(目录迁移一次成型)+P1-9;player_flow_sim P13(迁移+miss)\n+P1-12 零回归;stream/window/search/degrade/cover_chain/test_ncm_core(host+qemu) 全 PASS;\nbuild_ncm.sh -Wall -Wextra 0 warning。0 真实请求。新 ncm md5 a90ba8c3 (text1028458/bss1364484)。
This commit is contained in:
AskaEth committed 2026-09-02 13:51:36 +08:00
1 parent 986e3814bc
commit de18cef02c
11 files changed
+299 -64

No files matched your search

+69 -12
View File
@@ -8,6 +8,8 @@
* ③ ncm_cache_lookup 刷新 mtime(LRU:被访问的最近优先存活);
* ④ ncm_cache_set_limit_gb 越界钳制(1~100GB);
* ⑤ ncm_cache_clear 删除 song_* 全部(mp3/封面/残留 .tmp)。
* §NEW 缓存分目录:song_* 落 songs/、封面(song_cov*、cover_current.jpg)落 covers/;
* LRU/7 天回收只扫 songs/;⑩ 覆盖 ncm_cache_init 一次性把根混放文件迁入两子目录。
* 文件用 ftruncate 生成稀疏文件(逻辑大小可控,磁盘零消耗)。
*
* 编译(宿主,链接 core/ncm_cache.c + 本文件):
@@ -103,20 +105,27 @@ static int cached_exists(const char *id, const char *ext)
static unsigned long long mb(unsigned long long n) { return n * 1024ULL * 1024ULL; }
static void sweep_dir(const char *dir)
{
DIR *d = opendir(dir);
if (!d) return;
struct dirent *e;
while ((e = readdir(d))) {
if (strncmp(e->d_name, "song_", 5) != 0) continue;
char p[900];
snprintf(p, sizeof p, "%s/%s", dir, e->d_name);
unlink(p);
}
closedir(d);
}
/* 分目录后:song_* 落在 songs/(封面条目 song_cov* 在 covers/),清根 + 两个子目录。 */
static void cleanup(void)
{
/* 递归删 g_dir 下 song_*(恢复干净池) */
DIR *d = opendir(g_dir);
if (d) {
struct dirent *e;
while ((e = readdir(d))) {
if (strncmp(e->d_name, "song_", 5) != 0) continue;
char p[800];
snprintf(p, sizeof p, "%s/%s", g_dir, e->d_name);
unlink(p);
}
closedir(d);
}
char sub[640];
sweep_dir(g_dir);
snprintf(sub, sizeof sub, "%s/songs", g_dir); sweep_dir(sub);
snprintf(sub, sizeof sub, "%s/covers", g_dir); sweep_dir(sub);
}
int main(void)
@@ -330,6 +339,54 @@ int main(void)
CHECK(stat(lp, &st) == 0, "fresh .part kept (inflight/resumable)");
}
/* ⑩ §NEW 缓存分目录:ncm_cache_init 一次性把根混放的 song_*、cov*、cover_current.jpg 迁入
* songs//covers/(幂等),迁移后 lookup/total 照常在 songs/ 工作(覆盖场景 1)。 */
printf("== case 10: init legacy migration songs//covers/ one-shot ==\n");
cleanup();
{
char p[900], sp[900], path[900];
struct stat st;
/* 根放旧布局混血文件:歌曲(含双点畸形/在途 .tmp)+ 封面条目 + cover_current */
snprintf(p, sizeof p, "%s/song_legacy..mp3", g_dir);
FILE *f = fopen(p, "wb"); if (f) { fputs("OLD1", f); fclose(f); }
snprintf(p, sizeof p, "%s/song_legacy2.mp3", g_dir);
f = fopen(p, "wb"); if (f) { fputs("OLD2", f); fclose(f); }
snprintf(p, sizeof p, "%s/song_legacy3.mp3.tmp", g_dir); /* 在途 .tmp 随迁 */
f = fopen(p, "wb"); if (f) { fputs("TMP!", f); fclose(f); }
snprintf(p, sizeof p, "%s/song_covDEAD.jpg", g_dir);
f = fopen(p, "wb"); if (f) { fputs("COV", f); fclose(f); }
snprintf(p, sizeof p, "%s/cover_current.jpg", g_dir);
f = fopen(p, "wb"); if (f) { fputs("CUR", f); fclose(f); }
/* 重 init 触发迁移(memset 复位 + mkdir + 迁移);注意 limit 被复位到默认 10GB。 */
ncm_cache_init(&g_c, g_dir);
ncm_cache_set_limit_gb(&g_c, 1);
CHECK(g_c.legacy_migrated == 5, "legacy_migrated=%d (expect 5)", g_c.legacy_migrated);
snprintf(sp, sizeof sp, "%s/songs/song_legacy..mp3", g_dir);
CHECK(stat(sp, &st) == 0, "double-dot song moved into songs/");
snprintf(sp, sizeof sp, "%s/songs/song_legacy2.mp3", g_dir);
CHECK(stat(sp, &st) == 0, "song moved into songs/");
snprintf(sp, sizeof sp, "%s/songs/song_legacy3.mp3.tmp", g_dir);
CHECK(stat(sp, &st) == 0, "in-flight .tmp moved into songs/");
snprintf(sp, sizeof sp, "%s/covers/song_covDEAD.jpg", g_dir);
CHECK(stat(sp, &st) == 0, "cover cache entry moved into covers/");
snprintf(sp, sizeof sp, "%s/covers/cover_current.jpg", g_dir);
CHECK(stat(sp, &st) == 0, "cover_current.jpg moved into covers/");
snprintf(sp, sizeof sp, "%s/song_legacy2.mp3", g_dir);
CHECK(stat(sp, &st) != 0, "root song_* gone after migration");
/* 双点存量在 songs/ 内被 lookup 修成单点(不重下) */
CHECK(ncm_cache_lookup(&g_c, "legacy", "mp3", path, sizeof path) == NCM_OK,
"migrated double-dot lookup hit");
CHECK(strstr(path, "song_legacy.mp3") != NULL && strstr(path, "..") == NULL,
"double-dot fixed to single-dot (%s)", path);
/* total 只计 songs/(OLD1 4B + OLD2 4B;.tmp 半成品/封面不在 LRU 池) */
CHECK(ncm_cache_total_bytes(&g_c) == 8,
"total counts songs only (=%llu, expect 8)",
(unsigned long long)ncm_cache_total_bytes(&g_c));
}
cleanup();
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
return g_fail ? 1 : 0;