test(ncm): M6 缓存命名宿主单测(63 PASS)+ core 全量用例 + REPORT §39
- 新增 test/test_ncm_cache.c + build_ncm_cache_host.sh(纯 libc:真实 ncm_cache.c + ncm_tag.c,仅桩 strlcpy/mkdir_p/档位映射,不依赖 vendor/mbedtls) - 覆盖:实际档映射、降级后写入名、hires 命中已存 exhigh/lossless、低档不命中高档、 旧命名命中/扫描/删除/双点名迁移、列表去重、LRU/清空/删除兼容、level_from_path 边界 - test_ncm_core.c 新增 test_cache_actual_naming(接入 core 全量测试集,同语义断言) - REPORT §39:命名/命中/兼容方案、质量取舍与未执行项(vendor/交叉/真机)
This commit is contained in:
1 parent
68833cf642
commit
e60642217a
4 files changed
+678
No files matched your search
@@ -3369,3 +3369,63 @@ MP3 ≤128k→标准,>128k→极高 HQ。列表显示**实际档**(如 hires
|
|||||||
dispose 的 join 可能多等 ~0.3–1s。磁盘瞬时要 2× 单曲空间(失败自动降级裸流)。
|
dispose 的 join 可能多等 ~0.3–1s。磁盘瞬时要 2× 单曲空间(失败自动降级裸流)。
|
||||||
- **缓存队列点播的时长/封面**取自旁路 `meta/<id>.bin`(单次 ≤600B 读);无旁路的旧条目只有歌名/歌手/大小。
|
- **缓存队列点播的时长/封面**取自旁路 `meta/<id>.bin`(单次 ≤600B 读);无旁路的旧条目只有歌名/歌手/大小。
|
||||||
- **列表上限 512 条**(最近访问优先);池内更多时页脚仍显示展示条数,超出部分需 LRU 或搜索触达。
|
- **列表上限 512 条**(最近访问优先);池内更多时页脚仍显示展示条数,超出部分需 LRU 或搜索触达。
|
||||||
|
|
||||||
|
## 39. M6:缓存文件名改为「实际档」(2026-11-04)
|
||||||
|
|
||||||
|
用户原话:「有降级显示,但是那个缓存文件名误导人」。背景:现缓存命名 `song_<id>_<请求档>.<实际格式>`,
|
||||||
|
当服务端对无损档只给 mp3 时降级链落到 exhigh,文件名却仍写 hires(真机 `song_171650_hires.mp3`),名不副实。
|
||||||
|
|
||||||
|
### 39.1 设计(先写方案再动代码)
|
||||||
|
|
||||||
|
- **命名**:新写入一律 `song_<id>_<实际档>.<实际格式>`,实际档由 `ncm_cache_actual_level(req, 音频参数,
|
||||||
|
响应生效档)` 判定(**单一事实源**:写盘名 / 播放页显示 / 已缓存列表三处共用)。
|
||||||
|
规则:`min(请求档, 容器可承载档, 降级链生效档)`,只降不升——
|
||||||
|
mp3 码率 >128k→exhigh、≤128k→standard(mp3 永不高于极高,码率未知时也钳到极高);
|
||||||
|
flac 16bit/44.1k→lossless、24/48→hires、24/96→jyeffect、24/192→jymaster。
|
||||||
|
典型:hires 请求 + 320k mp3 响应 → `song_171650_exhigh.mp3`。
|
||||||
|
- **命中语义(同歌同档不重复下载)**:`ncm_cache_lookup` 在请求档精确 miss 后**只降不升**逐档下探
|
||||||
|
(`surround→sky→jymaster→jyeffect→hires→lossless→exhigh→higher→standard`,DOLBY 不在链内)。
|
||||||
|
于是 hires 请求命中已存的 `song_<id>_exhigh.mp3` / `song_<id>_lossless.flac`,不重下;低档请求
|
||||||
|
不会向上命中高档实体(不偷跑流量/存储)。`.part` 续传键也用实际档,同名才续传/commit。
|
||||||
|
- **旧文件兼容(不强制迁移)**:旧命名 `song_<id>_hires.mp3`(及无档位段 `song_<id>.mp3`、历史双点名)
|
||||||
|
原样保留:按原键可命中、可播放、可单曲删除、可 LRU 淘汰;列表按文件头探测显示实际档(M5 既有链路)。
|
||||||
|
播放/列表的生效档由 `ncm_cache_level_from_path`(文件名档位)+ 容器钳顶得出:mp3 实体不可能高于
|
||||||
|
极高,故旧 `hires.mp3` 标注「极高」,与实际一致。**不再**像 §NEW 任务2 那样把旧 mp3 改名 + 强制重下。
|
||||||
|
- **去重展示**:同曲同实际档、两种命名并存时(旧请求档名 + 新实际档名),`ncm_cache_scan` 只保留
|
||||||
|
最近访问的一条;不同实际档(如 lossless.flac + exhigh.mp3)照常各列一行。
|
||||||
|
|
||||||
|
### 39.2 改动
|
||||||
|
|
||||||
|
| 文件 | 改动 |
|
||||||
|
|---|---|
|
||||||
|
| `core/ncm_cache.c/.h` | 新增 `ncm_cache_actual_level`(容器/码率/位深/响应档→实际档,只降不升)、`ncm_cache_level_from_path`(文件名档位解析,含 `sky_c51`);`ncm_cache_lookup` 拆出精确查找 + 档位下探链;`ncm_cache_scan` 复用实际档判定并按 (id, actual_level, ext) 去重 |
|
||||||
|
| `app/play/player_manager.c` | 写盘键改用响应实际档(`ext` 判定后计算,`.part`/`open`/日志同键);`eff_level` = 实际档;命中路径按文件名档位 + mp3 钳顶标注实际档;删除旧「stale-degraded 改名+强制重下」与「无档位键迁移」块(老卡文件免迁移) |
|
||||||
|
| `test/test_ncm_cache.c` + `build_ncm_cache_host.sh` | 新增纯 libc 宿主单测:命名生成/解析、降级后写入名、hires 命中已存 exhigh/lossless、低档不命中高档、旧命名命中/扫描/删除、双点名迁移、列表去重、LRU/清空/删除对两种命名兼容、`level_from_path` 边界 |
|
||||||
|
| `test/test_ncm_core.c` | 新增 `test_cache_actual_naming`(同一组语义接入 core 全量测试集;本机缺 vendor 未整链,仅 `-fsyntax-only` 0 warning) |
|
||||||
|
| `bench/player_flow_sim.c` | 适配:补 `ncm_cache_actual_level`/`ncm_cache_level_from_path` 桩与 M5 后缺失的链接桩(`appstate_cache_*`、`ncm_tag_embed_file`);P7/P12 期望键改实际档;P13 改为「旧命名命中即播 + 实际档显示,不重下不迁移」 |
|
||||||
|
|
||||||
|
### 39.3 验证
|
||||||
|
|
||||||
|
| 项 | 结果 |
|
||||||
|
|---|---|
|
||||||
|
| `./build_ncm_cache_host.sh`(真实 `core/ncm_cache.c` + `core/ncm_tag.c`,仅桩 `ncm_strlcpy`/`mkdir_p`/档位映射) | **63 PASS / 0 FAIL**,`-Wall -Wextra` 0 warning |
|
||||||
|
| `player_manager.c` / `player_flow_sim.c` 语法+告警检查(`gcc -O2 -Wall -Wextra -fsyntax-only`,临时 jsmn 头垫片) | 0 warning |
|
||||||
|
| `bench/player_flow_sim.c` 链接(真实 player_manager + stream_src + mem_watch + meow_cachegov) | 链接通过(运行需可写 `/tmp`,本机 Termux 不可写,见 39.4) |
|
||||||
|
| `test_ncm_tag_host.sh` 既有链路 | 未改 `ncm_tag.c`,未重跑(非本批改动面) |
|
||||||
|
|
||||||
|
### 39.4 诚实出入 / 遗留
|
||||||
|
|
||||||
|
- **本机缺 vendor(mbedtls/jsmn/minimp3/qrcodegen)与交叉工具链**,`build_core.sh`/`build_ncm.sh`/
|
||||||
|
`build_ncm_host.sh` 未执行;`test_ncm_core_host` 未重编(M5 已记录同一限制)。新语义用
|
||||||
|
`test/test_ncm_cache_host`(真实 core 源)全量覆盖,构建环境需补跑 core 全量回归。
|
||||||
|
- **`player_flow_sim` 未在本机运行**:其路径硬编码 `/tmp`(Termux 不可写),M5 后还缺若干链接桩
|
||||||
|
(本批已补齐);需在常规构建机跑 P1–P15 全量。
|
||||||
|
- **质量取舍(任务口径)**:命中下探后,hires 请求会直接播放已存的 exhigh 实体,不再像 §NEW 任务2
|
||||||
|
那样强制为「更高档」重下。这是「实际档命名 + 同名去重」的必然结果(用户已接受降级显示);
|
||||||
|
如需「换高档时重取」,应作为显式「重新下载」交互另开需求。
|
||||||
|
- **flac 头参数在响应时点未知**:落盘名以降级链生效档为上限,下载完成后由列表探测显示实际档;
|
||||||
|
服务端「声称 hires 实为 16bit」的病态情况,文件名会高于列表实测档(既有取舍,未引入回归)。
|
||||||
|
- **旧命名文件的跨档复用**:旧 `song_<id>_hires.mp3`(实际 exhigh)在 hires 请求下直接命中;若用户
|
||||||
|
切到「极高」档再播同名曲,会按实际档新落一份 `song_<id>_exhigh.mp3`(一次性重下,旧文件不删,
|
||||||
|
列表按 (id,实际档,ext) 去重只显示一行,旧文件由 LRU/清空自然回收)。未对旧名做向上探测扫描
|
||||||
|
(避免每次 miss 扫目录 + 读多份文件头);如需「旧名跨档零重下」,可作为后续优化加一次目录级兼容探测。
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# build_ncm_cache_host.sh — 宿主单测:core/ncm_cache.c(M6 实际档命名 / 命中下探 / 旧名兼容 / LRU)。
|
||||||
|
# ncm_cache.c 依赖 ncm_tag.c(纯 libc)+ ncm_common/store/api 的少量符号,后者由测试文件内联桩提供,
|
||||||
|
# 故不依赖 mbedtls/jsmn/vendor(vendor 缺失时也能跑,$TMPDIR 纪律见 test 文件头)。
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
CC_HOST=${CC_HOST:-gcc}
|
||||||
|
OUT="test/test_ncm_cache_host"
|
||||||
|
$CC_HOST -O2 -Wall -Wextra -Icore \
|
||||||
|
core/ncm_cache.c core/ncm_tag.c test/test_ncm_cache.c -o "$OUT" \
|
||||||
|
|| { echo "build test_ncm_cache FAILED"; exit 1; }
|
||||||
|
echo "build OK: $OUT"
|
||||||
|
echo "run: ./$OUT"
|
||||||
@@ -0,0 +1,510 @@
|
|||||||
|
/* test/test_ncm_cache.c — M6 缓存命名「实际档」宿主单测(纯 libc,不依赖 mbedtls/jsmn)。
|
||||||
|
*
|
||||||
|
* 覆盖(对应任务:命名生成/解析、降级后写入名、hires 命中已存 exhigh、旧命名兼容、LRU 淘汰):
|
||||||
|
* ① ncm_cache_actual_level:请求档 + 容器/码率/位深 + 响应生效档 → 实际档(只降不升);
|
||||||
|
* ② 命名生成:实际档键 → songs/song_<id>_<实际档>.<实际格式>(降级后不再写请求档名);
|
||||||
|
* ③ 命中语义:hires 请求命中已存 song_<id>_exhigh.mp3 / song_<id>_lossless.flac(不重下),
|
||||||
|
* 低档请求不向上命中高档实体;
|
||||||
|
* ④ 旧命名兼容:song_<id>_hires.mp3(真机存量)可命中/可扫描/可删除,不迁移;
|
||||||
|
* 历史双点名 song_<id>..mp3 命中即迁移;LRU 淘汰对两种命名一视同仁;
|
||||||
|
* ⑤ 已缓存列表去重:同曲同实际档的旧名+新名只展示最近访问的一条;不同实际档都展示;
|
||||||
|
* ⑥ ncm_cache_level_from_path 解析(含 sky_c51 / 无档位段 / 封面名)。
|
||||||
|
*
|
||||||
|
* 编译/运行($TMPDIR 纪律;Termux 的 /tmp 不可写):./build_ncm_cache_host.sh && ./test/test_ncm_cache_host
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <utime.h>
|
||||||
|
|
||||||
|
#include "ncm_common.h" /* NCM_OK / NCM_ERR_* */
|
||||||
|
#include "ncm_cache.h"
|
||||||
|
|
||||||
|
/* ---- ncm_cache.c 依赖的核心符号(宿主单测最小桩;与 core 实现同语义) ---- */
|
||||||
|
size_t ncm_strlcpy(char *dst, const char *src, size_t sz)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
if (sz) {
|
||||||
|
while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; }
|
||||||
|
dst[n] = 0;
|
||||||
|
}
|
||||||
|
while (src[n]) n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
int ncm_store_mkdir_p(const char *path)
|
||||||
|
{
|
||||||
|
if (!path || !*path) return -1;
|
||||||
|
char tmp[512];
|
||||||
|
size_t len = strlen(path);
|
||||||
|
if (len >= sizeof tmp) return -1;
|
||||||
|
memcpy(tmp, path, len + 1);
|
||||||
|
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
|
||||||
|
for (size_t i = 1; i <= strlen(tmp); i++) {
|
||||||
|
if (tmp[i] == '/' || tmp[i] == '\0') {
|
||||||
|
char ch = tmp[i];
|
||||||
|
tmp[i] = '\0';
|
||||||
|
if (tmp[0] && mkdir(tmp, 0755) != 0 && errno != EEXIST) return -1;
|
||||||
|
tmp[i] = ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int ncm_store_read_cfg_int(const char *dir, const char *key, int def)
|
||||||
|
{ (void)dir; (void)key; return def; }
|
||||||
|
|
||||||
|
const char *ncm_api_level_str(int level)
|
||||||
|
{
|
||||||
|
switch (level) {
|
||||||
|
case NCM_LEVEL_SURROUND:
|
||||||
|
case NCM_LEVEL_SKY: return "sky";
|
||||||
|
case NCM_LEVEL_JYMASTER: return "jymaster";
|
||||||
|
case NCM_LEVEL_JYEFFECT: return "jyeffect";
|
||||||
|
case NCM_LEVEL_DOLBY: return "dolby";
|
||||||
|
case NCM_LEVEL_HIRES: return "hires";
|
||||||
|
case NCM_LEVEL_LOSSLESS: return "lossless";
|
||||||
|
case NCM_LEVEL_EXHIGH: return "exhigh";
|
||||||
|
case NCM_LEVEL_HIGHER: return "higher";
|
||||||
|
default: return "standard";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char *ncm_api_level_key(int level)
|
||||||
|
{
|
||||||
|
if (level == NCM_LEVEL_SURROUND) return "sky_c51";
|
||||||
|
return ncm_api_level_str(level);
|
||||||
|
}
|
||||||
|
int ncm_api_level_from_key(const char *key)
|
||||||
|
{
|
||||||
|
if (!key || !key[0]) return -1;
|
||||||
|
if (!strcmp(key, "sky_c51")) return NCM_LEVEL_SURROUND;
|
||||||
|
if (!strcmp(key, "sky")) return NCM_LEVEL_SKY;
|
||||||
|
if (!strcmp(key, "jymaster")) return NCM_LEVEL_JYMASTER;
|
||||||
|
if (!strcmp(key, "jyeffect")) return NCM_LEVEL_JYEFFECT;
|
||||||
|
if (!strcmp(key, "dolby")) return NCM_LEVEL_DOLBY;
|
||||||
|
if (!strcmp(key, "hires")) return NCM_LEVEL_HIRES;
|
||||||
|
if (!strcmp(key, "lossless")) return NCM_LEVEL_LOSSLESS;
|
||||||
|
if (!strcmp(key, "exhigh")) return NCM_LEVEL_EXHIGH;
|
||||||
|
if (!strcmp(key, "higher")) return NCM_LEVEL_HIGHER;
|
||||||
|
if (!strcmp(key, "standard")) return NCM_LEVEL_STANDARD;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int g_fail;
|
||||||
|
#define CHECK(cond, fmt, ...) do { \
|
||||||
|
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
|
||||||
|
else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define TMPDIR_PREFIX "ncm_cache_test"
|
||||||
|
|
||||||
|
static void fresh_dir(const char *sub)
|
||||||
|
{
|
||||||
|
char cmd[256];
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s/%s; mkdir -p %s/%s",
|
||||||
|
TMPDIR_PREFIX, sub, TMPDIR_PREFIX, sub);
|
||||||
|
if (system(cmd) != 0) { /* best-effort */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 样本:MP3 首帧(MPEG1 Layer III)+ 假音频字节 / FLAC STREAMINFO + 假帧 ---- */
|
||||||
|
static unsigned char *make_mp3(int kbps, size_t *len)
|
||||||
|
{
|
||||||
|
static unsigned char buf[512];
|
||||||
|
int idx = (kbps == 128) ? 9 : 14; /* 9=128k, 14=320k(MPEG1 L3 码率表) */
|
||||||
|
memset(buf, 0x11, sizeof buf);
|
||||||
|
buf[0] = 0xFF; buf[1] = 0xFB;
|
||||||
|
buf[2] = (unsigned char)((idx << 4) | 0x00); /* 44.1kHz(sr_idx=0) */
|
||||||
|
buf[3] = 0x00;
|
||||||
|
*len = sizeof buf;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char *make_flac(int bps, int sr, size_t *len)
|
||||||
|
{
|
||||||
|
static unsigned char buf[700];
|
||||||
|
size_t o = 0;
|
||||||
|
memset(buf, 0, sizeof buf);
|
||||||
|
memcpy(buf + o, "fLaC", 4); o += 4;
|
||||||
|
buf[o++] = 0x80; buf[o++] = 0x00; buf[o++] = 0x00; buf[o++] = 34; /* STREAMINFO, last */
|
||||||
|
unsigned char *si = buf + o;
|
||||||
|
si[0] = 0x10; si[1] = 0x00; si[2] = 0x10; si[3] = 0x00;
|
||||||
|
si[10] = (unsigned char)((sr >> 12) & 0xFF);
|
||||||
|
si[11] = (unsigned char)((sr >> 4) & 0xFF);
|
||||||
|
si[12] = (unsigned char)(((sr & 0x0F) << 4) | 0x02 | (((bps - 1) >> 4) & 0x01));
|
||||||
|
si[13] = (unsigned char)(((bps - 1) & 0x0F) << 4);
|
||||||
|
si[16] = 0xAC; si[17] = 0x44;
|
||||||
|
o += 34;
|
||||||
|
for (int i = 0; i < 512; i++) buf[o++] = (unsigned char)(0xF8 ^ i);
|
||||||
|
*len = o;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int file_put(const char *path, const void *data, size_t len)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
if (len && fwrite(data, 1, len, f) != len) { fclose(f); return -1; }
|
||||||
|
return fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int file_exists(const char *path)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
return stat(path, &st) == 0 && S_ISREG(st.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int file_exists_cstr(const char *dir, const char *name)
|
||||||
|
{
|
||||||
|
char p[512];
|
||||||
|
snprintf(p, sizeof p, "%s/songs/%s", dir, name);
|
||||||
|
return file_exists(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_mtime(const char *path, long t)
|
||||||
|
{
|
||||||
|
struct timespec ts[2];
|
||||||
|
ts[0].tv_sec = t; ts[0].tv_nsec = 0;
|
||||||
|
ts[1].tv_sec = t; ts[1].tv_nsec = 0;
|
||||||
|
utimensat(AT_FDCWD, path, ts, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 走生产写路径:实际档键 → ncm_cache_open/write/commit → 正式文件名。 */
|
||||||
|
static int cache_put(ncm_cache_t *c, const char *id, const char *level_key,
|
||||||
|
const char *ext, const void *data, size_t len,
|
||||||
|
char *out, size_t out_sz)
|
||||||
|
{
|
||||||
|
char key[64];
|
||||||
|
ncm_cache_writer_t *w;
|
||||||
|
snprintf(key, sizeof key, "%s_%s", id, level_key);
|
||||||
|
if (ncm_cache_open(c, key, ext, &w) != NCM_OK) return -1;
|
||||||
|
if (len && ncm_cache_write(w, (const unsigned char *)data, len) != NCM_OK) {
|
||||||
|
ncm_cache_abort(w);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (ncm_cache_commit(w) != 0) return -1;
|
||||||
|
return ncm_cache_path(c, key, ext, out, out_sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 直接落盘(旧命名/手工样本):songs/<name>,不走 writer。 */
|
||||||
|
static void raw_put(const ncm_cache_t *c, const char *name, const void *data, size_t len)
|
||||||
|
{
|
||||||
|
char p[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
snprintf(p, sizeof p, "%s/%s", ncm_cache_songs_dir(c), name);
|
||||||
|
file_put(p, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================ */
|
||||||
|
|
||||||
|
static void test_actual_level(void)
|
||||||
|
{
|
||||||
|
ncm_audio_info_t ai;
|
||||||
|
printf("== actual level mapping (single source of truth) ==\n");
|
||||||
|
|
||||||
|
memset(&ai, 0, sizeof ai);
|
||||||
|
snprintf(ai.container, sizeof ai.container, "mp3");
|
||||||
|
ai.bitrate_kbps = 320;
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_EXHIGH,
|
||||||
|
"hires + 320k mp3 -> exhigh");
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_EXHIGH) == NCM_LEVEL_EXHIGH,
|
||||||
|
"hires + 320k mp3 + chain eff exhigh -> exhigh");
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_STANDARD, &ai, -1) == NCM_LEVEL_STANDARD,
|
||||||
|
"standard request never upgraded by 320k mp3");
|
||||||
|
ai.bitrate_kbps = 128;
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_STANDARD,
|
||||||
|
"hires + 128k mp3 -> standard");
|
||||||
|
ai.bitrate_kbps = 0;
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_EXHIGH,
|
||||||
|
"hires + mp3 br unknown -> exhigh (mp3 never above exhigh)");
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_SURROUND, &ai, -1) == NCM_LEVEL_EXHIGH,
|
||||||
|
"surround + mp3 br unknown -> exhigh");
|
||||||
|
|
||||||
|
memset(&ai, 0, sizeof ai);
|
||||||
|
snprintf(ai.container, sizeof ai.container, "flac");
|
||||||
|
ai.bits_per_sample = 16; ai.sample_rate = 44100;
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_LOSSLESS,
|
||||||
|
"hires + 16bit/44.1k flac -> lossless");
|
||||||
|
ai.bits_per_sample = 24; ai.sample_rate = 48000;
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_HIRES,
|
||||||
|
"hires + 24bit/48k flac -> hires");
|
||||||
|
ai.sample_rate = 96000;
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, -1) == NCM_LEVEL_HIRES,
|
||||||
|
"hires + 24bit/96k flac -> hires (request caps)");
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_JYMASTER, &ai, -1) == NCM_LEVEL_JYEFFECT,
|
||||||
|
"jymaster + 24bit/96k flac -> jyeffect");
|
||||||
|
ai.sample_rate = 192000;
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_JYMASTER, &ai, -1) == NCM_LEVEL_JYMASTER,
|
||||||
|
"jymaster + 24bit/192k flac -> jymaster");
|
||||||
|
|
||||||
|
memset(&ai, 0, sizeof ai);
|
||||||
|
snprintf(ai.container, sizeof ai.container, "flac"); /* 仅知容器(响应时点) */
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_LOSSLESS) == NCM_LEVEL_LOSSLESS,
|
||||||
|
"hires + flac(header unknown) + chain eff lossless -> lossless");
|
||||||
|
CHECK(ncm_cache_actual_level(-1, &ai, NCM_LEVEL_HIRES) == NCM_LEVEL_HIRES,
|
||||||
|
"unknown request + chain eff hires -> hires");
|
||||||
|
CHECK(ncm_cache_actual_level(NCM_LEVEL_HIRES, NULL, -1) == NCM_LEVEL_HIRES,
|
||||||
|
"no probe + unknown chain -> request level");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_naming_degraded_write(void)
|
||||||
|
{
|
||||||
|
ncm_cache_t c;
|
||||||
|
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
size_t mlen;
|
||||||
|
unsigned char *mp3 = make_mp3(320, &mlen);
|
||||||
|
ncm_audio_info_t ai;
|
||||||
|
int actual;
|
||||||
|
|
||||||
|
printf("== naming: degraded write uses actual level (M6) ==\n");
|
||||||
|
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/naming");
|
||||||
|
fresh_dir("naming");
|
||||||
|
ncm_cache_init(&c, dir);
|
||||||
|
|
||||||
|
memset(&ai, 0, sizeof ai);
|
||||||
|
snprintf(ai.container, sizeof ai.container, "mp3");
|
||||||
|
ai.bitrate_kbps = 320;
|
||||||
|
actual = ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_EXHIGH);
|
||||||
|
CHECK(actual == NCM_LEVEL_EXHIGH, "degraded response actual level = exhigh");
|
||||||
|
CHECK(cache_put(&c, "171650", ncm_api_level_key(actual), "mp3",
|
||||||
|
mp3, mlen, path, sizeof path) == 0, "write with actual-level key");
|
||||||
|
CHECK(strstr(path, "song_171650_exhigh.mp3") != NULL,
|
||||||
|
"file name reflects actual level (%s)", path);
|
||||||
|
CHECK(file_exists(path), "actual-named file on disk");
|
||||||
|
CHECK(!file_exists_cstr(dir, "song_171650_hires.mp3"),
|
||||||
|
"no request-level name written for degraded download");
|
||||||
|
CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_EXHIGH,
|
||||||
|
"level parsed from new file name = exhigh");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_hit_semantics(void)
|
||||||
|
{
|
||||||
|
ncm_cache_t c;
|
||||||
|
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
size_t mlen, flen;
|
||||||
|
unsigned char *mp3 = make_mp3(320, &mlen);
|
||||||
|
unsigned char *flac = make_flac(16, 44100, &flen);
|
||||||
|
|
||||||
|
printf("== hit semantics: hires request finds stored actual-level entries ==\n");
|
||||||
|
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/hit");
|
||||||
|
fresh_dir("hit");
|
||||||
|
ncm_cache_init(&c, dir);
|
||||||
|
|
||||||
|
/* 已存:exhigh mp3(新命名)+ lossless flac(新命名) */
|
||||||
|
CHECK(cache_put(&c, "2001", "exhigh", "mp3", mp3, mlen, path, sizeof path) == 0,
|
||||||
|
"store exhigh mp3");
|
||||||
|
CHECK(cache_put(&c, "2002", "lossless", "flac", flac, flen, path, sizeof path) == 0,
|
||||||
|
"store lossless flac");
|
||||||
|
|
||||||
|
CHECK(ncm_cache_lookup(&c, "2001_hires", "mp3", path, sizeof path) == NCM_OK &&
|
||||||
|
strstr(path, "song_2001_exhigh.mp3") != NULL,
|
||||||
|
"hires request hits stored exhigh mp3 (no redownload)");
|
||||||
|
CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_EXHIGH,
|
||||||
|
"hit level = exhigh");
|
||||||
|
CHECK(ncm_cache_lookup(&c, "2002_hires", "flac", path, sizeof path) == NCM_OK &&
|
||||||
|
strstr(path, "song_2002_lossless.flac") != NULL,
|
||||||
|
"hires request hits stored lossless flac");
|
||||||
|
CHECK(ncm_cache_lookup(&c, "2002_exhigh", "flac", path, sizeof path) == NCM_ERR_NOENT,
|
||||||
|
"lower request never hits higher-tier entity (no upward lookup)");
|
||||||
|
CHECK(ncm_cache_lookup(&c, "2001_exhigh", "mp3", path, sizeof path) == NCM_OK,
|
||||||
|
"exact level still hits directly");
|
||||||
|
CHECK(ncm_cache_lookup(&c, "2001_standard", "mp3", path, sizeof path) == NCM_ERR_NOENT,
|
||||||
|
"standard request does not use 320k exhigh file");
|
||||||
|
CHECK(ncm_cache_lookup(&c, "9999_hires", "mp3", path, sizeof path) == NCM_ERR_NOENT,
|
||||||
|
"unknown song misses");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_legacy_compat(void)
|
||||||
|
{
|
||||||
|
ncm_cache_t c;
|
||||||
|
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
size_t mlen;
|
||||||
|
unsigned char *mp3 = make_mp3(320, &mlen);
|
||||||
|
|
||||||
|
printf("== legacy naming compatibility (read/delete/scan, no migration) ==\n");
|
||||||
|
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/legacy");
|
||||||
|
fresh_dir("legacy");
|
||||||
|
ncm_cache_init(&c, dir);
|
||||||
|
|
||||||
|
/* 真机存量形态:请求档 hires + 实际 320k mp3 */
|
||||||
|
raw_put(&c, "song_171650_hires.mp3", mp3, mlen);
|
||||||
|
CHECK(ncm_cache_lookup(&c, "171650_hires", "mp3", path, sizeof path) == NCM_OK &&
|
||||||
|
strstr(path, "song_171650_hires.mp3") != NULL,
|
||||||
|
"legacy hires.mp3 still playable at its request key");
|
||||||
|
CHECK(file_exists(path), "legacy file untouched (not renamed)");
|
||||||
|
CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_HIRES,
|
||||||
|
"path level is the legacy request level (hires)");
|
||||||
|
|
||||||
|
/* 扫描:旧文件的档位段保留(删除/点播定位用),实际档由文件头纠正 */
|
||||||
|
{
|
||||||
|
ncm_cache_item_t items[8];
|
||||||
|
int n = ncm_cache_scan(&c, items, 8);
|
||||||
|
CHECK(n == 1, "scan lists legacy entry (n=%d)", n);
|
||||||
|
CHECK(n == 1 && strcmp(items[0].id, "171650") == 0 &&
|
||||||
|
strcmp(items[0].level, "hires") == 0 && strcmp(items[0].ext, "mp3") == 0,
|
||||||
|
"legacy item id/level/ext parsed");
|
||||||
|
CHECK(n == 1 && items[0].actual_level == NCM_LEVEL_EXHIGH,
|
||||||
|
"legacy hires.mp3 displays actual level exhigh (got %d)",
|
||||||
|
n == 1 ? items[0].actual_level : -999);
|
||||||
|
if (n == 1)
|
||||||
|
CHECK(ncm_cache_remove(&c, items[0].id, items[0].level, items[0].ext) == NCM_OK,
|
||||||
|
"delete by scanned legacy key");
|
||||||
|
CHECK(!file_exists_cstr(dir, "song_171650_hires.mp3"),
|
||||||
|
"legacy file removed by list delete path");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 历史双点名 song_<id>..mp3:命中即迁移到单点名 */
|
||||||
|
raw_put(&c, "song_171651..mp3", mp3, mlen);
|
||||||
|
CHECK(ncm_cache_lookup(&c, "171651", "mp3", path, sizeof path) == NCM_OK &&
|
||||||
|
strstr(path, "song_171651.mp3") != NULL && strstr(path, "..") == NULL,
|
||||||
|
"legacy double-dot migrated on lookup (%s)", path);
|
||||||
|
CHECK(!file_exists_cstr(dir, "song_171651..mp3"), "double-dot source gone");
|
||||||
|
|
||||||
|
/* 旧无档位键 song_<id>.mp3:任何请求档都回退命中(调用方第 3 步 lookup(id)) */
|
||||||
|
CHECK(ncm_cache_lookup(&c, "171651", "mp3", path, sizeof path) == NCM_OK,
|
||||||
|
"no-level legacy key still hit");
|
||||||
|
CHECK(ncm_cache_remove(&c, "171651", "", "mp3") == NCM_OK, "remove no-level legacy key");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scan_dedup(void)
|
||||||
|
{
|
||||||
|
ncm_cache_t c;
|
||||||
|
char dir[256], path[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
size_t mlen, flen;
|
||||||
|
unsigned char *mp3 = make_mp3(320, &mlen);
|
||||||
|
unsigned char *flac = make_flac(16, 44100, &flen);
|
||||||
|
|
||||||
|
printf("== cache list dedup: same song same actual level ==\n");
|
||||||
|
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/dedup");
|
||||||
|
fresh_dir("dedup");
|
||||||
|
ncm_cache_init(&c, dir);
|
||||||
|
|
||||||
|
/* 同曲同实际档、两种命名并存(旧 song_3001_hires.mp3 新 song_3001_exhigh.mp3) */
|
||||||
|
raw_put(&c, "song_3001_hires.mp3", mp3, mlen);
|
||||||
|
raw_put(&c, "song_3001_exhigh.mp3", mp3, mlen);
|
||||||
|
{
|
||||||
|
char p[512];
|
||||||
|
snprintf(p, sizeof p, "%s/songs/song_3001_hires.mp3", dir);
|
||||||
|
set_mtime(p, 1000);
|
||||||
|
snprintf(p, sizeof p, "%s/songs/song_3001_exhigh.mp3", dir);
|
||||||
|
set_mtime(p, 2000);
|
||||||
|
}
|
||||||
|
CHECK(cache_put(&c, "3002", "lossless", "flac", flac, flen, path, sizeof path) == 0,
|
||||||
|
"store lossless entry for tier-variety case");
|
||||||
|
CHECK(cache_put(&c, "3002", "exhigh", "mp3", mp3, mlen, path, sizeof path) == 0,
|
||||||
|
"store exhigh entry for same song");
|
||||||
|
{
|
||||||
|
ncm_cache_item_t items[8];
|
||||||
|
int n = ncm_cache_scan(&c, items, 8);
|
||||||
|
int d3001 = 0, d3002 = 0;
|
||||||
|
int lv3001 = -999;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (strcmp(items[i].id, "3001") == 0) { d3001++; lv3001 = items[i].actual_level; }
|
||||||
|
if (strcmp(items[i].id, "3002") == 0) d3002++;
|
||||||
|
}
|
||||||
|
CHECK(d3001 == 1, "same song same actual level deduped to 1 row (got %d)", d3001);
|
||||||
|
CHECK(lv3001 == NCM_LEVEL_EXHIGH, "deduped row keeps actual level exhigh (got %d)", lv3001);
|
||||||
|
CHECK(d3002 == 2, "different actual levels both listed (got %d)", d3002);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_lru_compat(void)
|
||||||
|
{
|
||||||
|
ncm_cache_t c;
|
||||||
|
char dir[256];
|
||||||
|
size_t mlen;
|
||||||
|
unsigned char *mp3 = make_mp3(320, &mlen);
|
||||||
|
char oldp[512], newp[512];
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
printf("== LRU eviction compatibility (legacy + actual naming) ==\n");
|
||||||
|
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/lru");
|
||||||
|
fresh_dir("lru");
|
||||||
|
ncm_cache_init(&c, dir);
|
||||||
|
ncm_cache_set_limit_gb(&c, 1); /* 1GB 最小上限 */
|
||||||
|
|
||||||
|
/* 稀疏文件:旧命名 700MB(最旧)+ 新命名 400MB(最新)→ 超限删最旧 */
|
||||||
|
snprintf(oldp, sizeof oldp, "%s/songs/song_4001_hires.mp3", dir);
|
||||||
|
snprintf(newp, sizeof newp, "%s/songs/song_4002_exhigh.mp3", dir);
|
||||||
|
fd = open(oldp, O_WRONLY | O_CREAT, 0644); if (fd >= 0) { if (ftruncate(fd, 700L << 20) != 0) {} close(fd); }
|
||||||
|
fd = open(newp, O_WRONLY | O_CREAT, 0644); if (fd >= 0) { if (ftruncate(fd, 400L << 20) != 0) {} close(fd); }
|
||||||
|
set_mtime(oldp, 1000);
|
||||||
|
set_mtime(newp, 2000);
|
||||||
|
|
||||||
|
CHECK(ncm_cache_total_bytes(&c) == (700ULL << 20) + (400ULL << 20),
|
||||||
|
"total counts both naming schemes (%llu)",
|
||||||
|
(unsigned long long)ncm_cache_total_bytes(&c));
|
||||||
|
CHECK(ncm_cache_evict(&c) == NCM_OK, "evict runs");
|
||||||
|
CHECK(!file_exists(oldp), "oldest legacy-named entry evicted");
|
||||||
|
CHECK(file_exists(newp), "newest actual-named entry kept");
|
||||||
|
CHECK(ncm_cache_total_bytes(&c) == (400ULL << 20), "total after evict = 400MB (%llu)",
|
||||||
|
(unsigned long long)ncm_cache_total_bytes(&c));
|
||||||
|
ncm_cache_set_limit_gb(&c, 10);
|
||||||
|
|
||||||
|
/* 清空兼容两种命名 */
|
||||||
|
raw_put(&c, "song_4003_hires.mp3", mp3, mlen);
|
||||||
|
raw_put(&c, "song_4004_exhigh.mp3", mp3, mlen);
|
||||||
|
CHECK(ncm_cache_clear(&c) == NCM_OK, "clear runs");
|
||||||
|
CHECK(!file_exists(newp) && !file_exists_cstr(dir, "song_4003_hires.mp3") &&
|
||||||
|
!file_exists_cstr(dir, "song_4004_exhigh.mp3"),
|
||||||
|
"clear removes both naming schemes");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_remove_compat(void)
|
||||||
|
{
|
||||||
|
ncm_cache_t c;
|
||||||
|
char dir[256];
|
||||||
|
size_t mlen;
|
||||||
|
unsigned char *mp3 = make_mp3(320, &mlen);
|
||||||
|
|
||||||
|
printf("== remove compatibility ==\n");
|
||||||
|
snprintf(dir, sizeof dir, TMPDIR_PREFIX "/remove");
|
||||||
|
fresh_dir("remove");
|
||||||
|
ncm_cache_init(&c, dir);
|
||||||
|
|
||||||
|
raw_put(&c, "song_5001_hires.mp3", mp3, mlen); /* 旧请求档名 */
|
||||||
|
raw_put(&c, "song_5002_exhigh.mp3", mp3, mlen); /* 新实际档名 */
|
||||||
|
raw_put(&c, "song_5003.mp3", mp3, mlen); /* 旧无档位键 */
|
||||||
|
CHECK(ncm_cache_remove(&c, "5001", "hires", "mp3") == NCM_OK, "remove legacy request-level name");
|
||||||
|
CHECK(ncm_cache_remove(&c, "5002", "exhigh", "mp3") == NCM_OK, "remove actual-level name");
|
||||||
|
CHECK(ncm_cache_remove(&c, "5003", "", "mp3") == NCM_OK, "remove no-level name");
|
||||||
|
CHECK(ncm_cache_remove(&c, "5002", "exhigh", "mp3") == NCM_ERR_NOENT, "second remove = NOENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_level_from_path(void)
|
||||||
|
{
|
||||||
|
printf("== level_from_path parsing ==\n");
|
||||||
|
CHECK(ncm_cache_level_from_path("/x/songs/song_171650_exhigh.mp3") == NCM_LEVEL_EXHIGH,
|
||||||
|
"song_<id>_exhigh.mp3 -> exhigh");
|
||||||
|
CHECK(ncm_cache_level_from_path("song_171650_sky_c51.flac") == NCM_LEVEL_SURROUND,
|
||||||
|
"song_<id>_sky_c51.flac -> surround");
|
||||||
|
CHECK(ncm_cache_level_from_path("song_171650.mp3") == -1, "no level segment -> -1");
|
||||||
|
CHECK(ncm_cache_level_from_path("cover_current.jpg") == -1, "cover name -> -1");
|
||||||
|
CHECK(ncm_cache_level_from_path("/tmp/pf_hires.mp3") == NCM_LEVEL_HIRES,
|
||||||
|
"bare <name>_<level>.ext parsed");
|
||||||
|
CHECK(ncm_cache_level_from_path(NULL) == -1 && ncm_cache_level_from_path("") == -1,
|
||||||
|
"NULL/empty -> -1");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const char *base = getenv("TMPDIR");
|
||||||
|
printf("ncm_cache naming self-test (M6)\n");
|
||||||
|
if (base && base[0] && chdir(base) != 0) printf(" warn: chdir(%s) failed\n", base);
|
||||||
|
{
|
||||||
|
char cmd[128];
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s; mkdir -p %s", TMPDIR_PREFIX, TMPDIR_PREFIX);
|
||||||
|
if (system(cmd) != 0) { /* best-effort */ }
|
||||||
|
}
|
||||||
|
test_actual_level();
|
||||||
|
test_naming_degraded_write();
|
||||||
|
test_hit_semantics();
|
||||||
|
test_legacy_compat();
|
||||||
|
test_scan_dedup();
|
||||||
|
test_lru_compat();
|
||||||
|
test_remove_compat();
|
||||||
|
test_level_from_path();
|
||||||
|
printf("\n%s\n", g_fail ? "== RESULT: FAIL ==" : "== RESULT: PASS ==");
|
||||||
|
return g_fail ? 1 : 0;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "ncm.h"
|
#include "ncm.h"
|
||||||
#include "mbedtls/aes.h"
|
#include "mbedtls/aes.h"
|
||||||
@@ -305,6 +306,100 @@ static void test_cache_tag(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- M6:缓存文件名 = 实际档(响应档) ----------
|
||||||
|
* 用户反馈「有降级显示,但是那个缓存文件名误导人」:hires 请求被降级链落到 exhigh mp3 时,
|
||||||
|
* 旧命名仍写 song_<id>_hires.mp3。本测试断言:①写盘名用实际档;②hires 请求命中已存的
|
||||||
|
* exhigh 实体(不重下);③旧命名继续可命中/可扫描/可删除、不迁移;④列表去重;⑤LRU 兼容。 */
|
||||||
|
static void test_cache_actual_naming(void) {
|
||||||
|
printf("== cache actual-level naming (M6) ==\n");
|
||||||
|
ncm_cache_t c;
|
||||||
|
const char *base = getenv("TMPDIR");
|
||||||
|
char dir[512], path[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
unsigned char mp3[600];
|
||||||
|
ncm_cache_item_t items[16];
|
||||||
|
|
||||||
|
snprintf(dir, sizeof dir, "%s/ncm_cache_m6_%d", (base && base[0]) ? base : "/tmp", (int)getpid());
|
||||||
|
{
|
||||||
|
char cmd[640];
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s; mkdir -p %s", dir, dir);
|
||||||
|
if (system(cmd) != 0) { /* best-effort */ }
|
||||||
|
}
|
||||||
|
ncm_cache_init(&c, dir);
|
||||||
|
|
||||||
|
memset(mp3, 0x11, sizeof mp3);
|
||||||
|
mp3[0] = 0xFF; mp3[1] = 0xFB; mp3[2] = 0xE0; mp3[3] = 0x00; /* 320kbps / 44.1kHz */
|
||||||
|
|
||||||
|
/* ① 降级响应(hires 请求 → 320k mp3)→ 实际档 exhigh → 写盘名 song_<id>_exhigh.mp3 */
|
||||||
|
{
|
||||||
|
ncm_audio_info_t ai;
|
||||||
|
char key[64];
|
||||||
|
ncm_cache_writer_t *w;
|
||||||
|
memset(&ai, 0, sizeof ai);
|
||||||
|
snprintf(ai.container, sizeof ai.container, "mp3");
|
||||||
|
ai.bitrate_kbps = 320;
|
||||||
|
int actual = ncm_cache_actual_level(NCM_LEVEL_HIRES, &ai, NCM_LEVEL_EXHIGH);
|
||||||
|
CHECK(actual == NCM_LEVEL_EXHIGH, "actual level for hires+320k mp3 = exhigh (got %d)", actual);
|
||||||
|
snprintf(key, sizeof key, "171650_%s", ncm_api_level_key(actual));
|
||||||
|
CHECKRC(ncm_cache_open(&c, key, "mp3", &w), "open with actual-level key");
|
||||||
|
CHECKRC(ncm_cache_write(w, mp3, sizeof mp3), "write degraded body");
|
||||||
|
CHECKRC(ncm_cache_commit(w), "commit actual-level entry");
|
||||||
|
CHECKRC(ncm_cache_lookup(&c, "171650_hires", "mp3", path, sizeof path),
|
||||||
|
"hires request hits stored exhigh entry");
|
||||||
|
CHECK(strstr(path, "song_171650_exhigh.mp3") != NULL, "hit path = actual-level name (%s)", path);
|
||||||
|
CHECK(ncm_cache_level_from_path(path) == NCM_LEVEL_EXHIGH, "level parsed from path = exhigh");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ② 旧命名存量(真机 song_<id>_hires.mp3):按原键命中,不迁移;低档请求不向上命中 */
|
||||||
|
{
|
||||||
|
char p[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
CHECKRC(ncm_cache_path(&c, "171651_hires", "mp3", p, sizeof p), "legacy path");
|
||||||
|
FILE *f = fopen(p, "wb");
|
||||||
|
if (f) { fwrite(mp3, 1, sizeof mp3, f); fclose(f); }
|
||||||
|
CHECKRC(ncm_cache_lookup(&c, "171651_hires", "mp3", path, sizeof path),
|
||||||
|
"legacy hires.mp3 hit at its own key");
|
||||||
|
CHECK(strstr(path, "song_171651_hires.mp3") != NULL, "legacy name not migrated (%s)", path);
|
||||||
|
CHECK(ncm_cache_lookup(&c, "171651_exhigh", "mp3", path, sizeof path) == NCM_ERR_NOENT,
|
||||||
|
"lower request does not hit higher-named legacy entry (down-only ladder)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ③ 列表:旧名条目的档位段保留、实际档由文件头纠正;同曲同实际档的旧名+新名去重 */
|
||||||
|
{
|
||||||
|
char p[NCM_CACHE_DIR_MAX + 64];
|
||||||
|
CHECKRC(ncm_cache_path(&c, "171652_hires", "mp3", p, sizeof p), "legacy dup path");
|
||||||
|
FILE *f = fopen(p, "wb");
|
||||||
|
if (f) { fwrite(mp3, 1, sizeof mp3, f); fclose(f); }
|
||||||
|
CHECKRC(ncm_cache_path(&c, "171652_exhigh", "mp3", p, sizeof p), "new dup path");
|
||||||
|
f = fopen(p, "wb");
|
||||||
|
if (f) { fwrite(mp3, 1, sizeof mp3, f); fclose(f); }
|
||||||
|
int n = ncm_cache_scan(&c, items, 16);
|
||||||
|
int seen_old = 0, seen_dup = 0, lv_old = -999;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (strcmp(items[i].id, "171651") == 0) {
|
||||||
|
seen_old = 1; lv_old = items[i].actual_level;
|
||||||
|
CHECK(strcmp(items[i].level, "hires") == 0, "legacy item keeps file-name level (hires)");
|
||||||
|
}
|
||||||
|
if (strcmp(items[i].id, "171652") == 0) seen_dup++;
|
||||||
|
}
|
||||||
|
CHECK(seen_old && lv_old == NCM_LEVEL_EXHIGH,
|
||||||
|
"legacy hires.mp3 displays actual exhigh (got %d)", lv_old);
|
||||||
|
CHECK(seen_dup == 1, "same song same actual level deduped to 1 row (got %d)", seen_dup);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ④ 删除/淘汰对两种命名一视同仁 */
|
||||||
|
CHECKRC(ncm_cache_remove(&c, "171651", "hires", "mp3"), "remove legacy-named entry");
|
||||||
|
CHECK(ncm_cache_lookup(&c, "171651_hires", "mp3", path, sizeof path) == NCM_ERR_NOENT,
|
||||||
|
"legacy entry gone after remove");
|
||||||
|
c.limit_bytes = 1;
|
||||||
|
CHECKRC(ncm_cache_evict(&c), "evict mixed naming pool");
|
||||||
|
CHECK(ncm_cache_scan(&c, items, 16) == 0, "all entries evicted regardless of naming");
|
||||||
|
ncm_cache_set_limit_gb(&c, 10);
|
||||||
|
{
|
||||||
|
char cmd[640];
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s", dir);
|
||||||
|
if (system(cmd) != 0) { /* best-effort */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void test_qr(void) {
|
static void test_qr(void) {
|
||||||
printf("== qrcodegen ==\n");
|
printf("== qrcodegen ==\n");
|
||||||
unsigned char mods[177 * 177];
|
unsigned char mods[177 * 177];
|
||||||
@@ -679,6 +774,7 @@ int main(int argc, char **argv) {
|
|||||||
test_store_cookie();
|
test_store_cookie();
|
||||||
test_cache();
|
test_cache();
|
||||||
test_cache_tag();
|
test_cache_tag();
|
||||||
|
test_cache_actual_naming();
|
||||||
test_qr();
|
test_qr();
|
||||||
test_cookie_merge();
|
test_cookie_merge();
|
||||||
test_eapi_musicu_hdr();
|
test_eapi_musicu_hdr();
|
||||||
|
|||||||
Reference in new issue
Block a user