/* logic/appstate.c — 应用全局状态:ctx/登录/歌单/歌曲/设置。仅依赖核心库 + SDK meow_config_*。 */ #include "../ncm_app_internal.h" #include #include #include #define NCM_DEFAULT_DATA "/usr/data/mnt/sd_0/MeowData/dev.player.ncm/data" int appstate_init(void) { const char *dd = getenv("MEOW_DATA_DIR"); if (!dd || !dd[0]) dd = NCM_DEFAULT_DATA; strncpy(g_st.data_dir, dd, sizeof g_st.data_dir - 1); g_st.data_dir[sizeof g_st.data_dir - 1] = 0; 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); else ncm_ctx_default_cache_dir(g_st.data_dir, g_st.cache_dir, sizeof g_st.cache_dir); appstate_load_settings(); g_st.logged_in = (g_st.ctx.cookie[0] != 0); g_st.active_playlist = -1; g_st.playing_song = -1; g_st.qr_size = 0; g_st.login_state = NCM_LOGIN_PENDING; /* uid/nickname 从配置恢复(登录时落盘);uid 缺失时不在此阻塞校验——由 home_reload 拉歌单时 * appstate_ensure_uid 触发 login_status_check(启动快,避免首屏长时间网络阻塞)。 */ meow_config_get("ncm.uid", g_st.uid, sizeof g_st.uid); meow_config_get("ncm.nickname", g_st.nickname, sizeof g_st.nickname); return 0; } void appstate_load_settings(void) { g_st.cache_limit_gb = meow_config_int("ncm.cache_limit_gb", 10); if (g_st.cache_limit_gb < 1) g_st.cache_limit_gb = 1; if (g_st.cache_limit_gb > 100) g_st.cache_limit_gb = 100; g_st.level = meow_config_int("ncm.level", NCM_LEVEL_HIRES); if (g_st.level < NCM_LEVEL_STANDARD || g_st.level > NCM_LEVEL_HIRES) g_st.level = NCM_LEVEL_HIRES; ncm_cache_set_limit_gb(&g_st.ctx.cache, g_st.cache_limit_gb); g_st.ctx.default_level = g_st.level; } void appstate_save_settings(void) { char b[16]; snprintf(b, sizeof b, "%d", g_st.cache_limit_gb); meow_config_set("ncm.cache_limit_gb", b); snprintf(b, sizeof b, "%d", g_st.level); meow_config_set("ncm.level", b); ncm_cache_set_limit_gb(&g_st.ctx.cache, g_st.cache_limit_gb); g_st.ctx.default_level = g_st.level; } void appstate_set_logged_in(const char *uid, const char *nickname) { if (uid && uid[0]) { ncm_strlcpy(g_st.uid, uid, sizeof g_st.uid); meow_config_set("ncm.uid", uid); } if (nickname && nickname[0]) { ncm_strlcpy(g_st.nickname, nickname, sizeof g_st.nickname); meow_config_set("ncm.nickname", nickname); } g_st.logged_in = 1; } void appstate_logout(void) { ncm_ctx_clear_cookie(&g_st.ctx); g_st.logged_in = 0; g_st.uid[0] = 0; g_st.nickname[0] = 0; g_st.active_playlist = -1; g_st.playing_song = -1; g_st.song_count = 0; meow_config_remove("ncm.uid"); meow_config_remove("ncm.nickname"); cover_cache_reset(); } /* 若 uid 缺失(cookie 在但没存 uid),用 login_status_check 校验并取回。返回 0=成功。 */ int appstate_ensure_uid(void) { if (!g_st.logged_in || !g_st.ctx.cookie[0]) return NCM_ERR_AUTH; if (g_st.uid[0]) return NCM_OK; char uid[32], nick[128]; int rc = ncm_login_status_check(g_st.ctx.device_id, g_st.ctx.cookie, uid, sizeof uid, nick, sizeof nick); if (rc == NCM_OK) { appstate_set_logged_in(uid, nick); return NCM_OK; } /* 会话失效:清 cookie → 未登录 */ if (rc == NCM_ERR_AUTH) { ncm_ctx_clear_cookie(&g_st.ctx); g_st.logged_in = 0; } return rc; } int appstate_load_playlists(void) { if (!g_st.logged_in) return NCM_ERR_AUTH; int rc = appstate_ensure_uid(); if (rc != NCM_OK) return rc; memset(g_st.playlists, 0, sizeof g_st.playlists); int n = 0; rc = ncm_api_user_playlist(g_st.ctx.device_id, g_st.ctx.cookie, g_st.uid, NCM_PLAYLIST_MAX, 0, g_st.playlists, NCM_PLAYLIST_MAX, &n); g_st.playlist_count = (rc == NCM_OK) ? n : 0; meow_log(MEOW_LOG_INFO, "home: playlists rc=%d count=%d uid=%s", rc, g_st.playlist_count, g_st.uid); return rc; } int appstate_load_playlist_songs_page(int idx, int offset, int limit) { if (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]; if (limit <= 0 || limit > NCM_SONG_BATCH) limit = NCM_SONG_BATCH; 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)。 */ if (offset == 0) { /* 重头:重建 trackIds 索引 + 清空已物化歌曲(song_count 无论成败都归零)。 */ memset(g_st.song_ids, 0, sizeof g_st.song_ids); memset(g_st.songs, 0, sizeof g_st.songs); g_st.song_count = 0; g_st.track_ids_count = 0; 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); if (rc != NCM_OK) return rc; g_st.track_ids_count = idn; /* 首屏只需第 0 页:song_detail 只拉前 limit 首,其余待滚动触底再拉(分页驱动)。 */ } 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; } 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; } void appstate_apply_playlist_cover(int idx) { 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); } /* 设置播放页当前歌曲 + 封面 URL(player 页 UI 读)。idx=-1 表示清除。 */ void appstate_set_playing_song(int idx, const char *cover_url) { g_st.playing_song = idx; if (cover_url && cover_url[0]) ncm_strlcpy(g_st.cover_url, cover_url, sizeof g_st.cover_url); } int appstate_current_playing(void) { if (g_st.playing_song < 0 || g_st.playing_song >= g_st.song_count) return -1; return g_st.playing_song; }