using System; using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Networking; public class DownloadManager : MonoBehaviour { [SerializeField] private string baseUrl = "http://whdwo798.synology.me:8180/beatsaber"; private static string CacheRoot => Path.Combine(Application.temporaryCachePath, "beatsaber"); // ── Public API ─────────────────────────────────────────── public void FetchSongsList(Action onSuccess, Action onError = null) { StartCoroutine(GetText($"{baseUrl}/songs.json", json => { SongsList list = JsonUtility.FromJson(json); if (list == null) onError?.Invoke("songs.json 파싱 실패"); else onSuccess?.Invoke(list); }, onError)); } // 오디오 + 선택 난이도 맵 다운로드 public void DownloadSong(SongInfo song, string difficulty, Action onProgress, Action onComplete, Action onError = null) { StartCoroutine(DownloadSongCoroutine(song, difficulty, onProgress, onComplete, onError)); } public void DeleteSong(string songId) { string dir = SongDir(songId); if (Directory.Exists(dir)) { Directory.Delete(dir, recursive: true); Debug.Log($"[DownloadManager] 삭제: {songId}"); } } public void DeleteDifficulty(SongInfo song, string difficulty) { string path = MapPath(song, difficulty); if (path != null && File.Exists(path)) File.Delete(path); } public bool IsSongDownloaded(string songId) => File.Exists(AudioPath(songId)); public bool IsDifficultyDownloaded(SongInfo song, string difficulty) { string path = MapPath(song, difficulty); return path != null && File.Exists(path); } // Spawner에서 재생 경로를 얻을 때 사용 public string AudioPath(string songId) => Path.Combine(SongDir(songId), $"{songId}.mp3"); public string MapPath(SongInfo song, string difficulty) { DifficultyInfo info = song.difficulties.Get(difficulty); if (info == null) return null; return Path.Combine(SongDir(song.id), Path.GetFileName(info.mapFile)); } // ── 내부 구현 ───────────────────────────────────────────── private IEnumerator DownloadSongCoroutine(SongInfo song, string difficulty, Action onProgress, Action onComplete, Action onError) { Directory.CreateDirectory(SongDir(song.id)); // 1단계: 오디오 (70%) string audioPath = AudioPath(song.id); if (!File.Exists(audioPath)) { bool failed = false; yield return DownloadFile( $"{baseUrl}/{song.audioFile}", audioPath, p => onProgress?.Invoke(p * 0.7f), err => { onError?.Invoke(err); failed = true; }); if (failed) yield break; } // 2단계: 맵 파일 (30%) DifficultyInfo diffInfo = song.difficulties.Get(difficulty); if (diffInfo == null) { onError?.Invoke($"난이도 '{difficulty}' 없음"); yield break; } string mapPath = MapPath(song, difficulty); if (!File.Exists(mapPath)) { bool failed = false; yield return DownloadFile( $"{baseUrl}/{diffInfo.mapFile}", mapPath, p => onProgress?.Invoke(0.7f + p * 0.3f), err => { onError?.Invoke(err); failed = true; }); if (failed) yield break; } onProgress?.Invoke(1f); onComplete?.Invoke(); Debug.Log($"[DownloadManager] 완료: {song.title} ({difficulty})"); } private IEnumerator DownloadFile(string url, string savePath, Action onProgress, Action onError) { using var req = UnityWebRequest.Get(url); req.downloadHandler = new DownloadHandlerFile(savePath); req.SendWebRequest(); while (!req.isDone) { onProgress?.Invoke(req.downloadProgress); yield return null; } if (req.result != UnityWebRequest.Result.Success) { if (File.Exists(savePath)) File.Delete(savePath); onError?.Invoke($"다운로드 실패: {url} — {req.error}"); } } private IEnumerator GetText(string url, Action onSuccess, Action onError) { using var req = UnityWebRequest.Get(url); yield return req.SendWebRequest(); if (req.result != UnityWebRequest.Result.Success) onError?.Invoke($"요청 실패: {url} — {req.error}"); else onSuccess?.Invoke(req.downloadHandler.text); } private static string SongDir(string songId) => Path.Combine(CacheRoot, songId); }