노래 만들기 수정 — NAS 업로드 완성, Easy 제거 및 ExpertPlus 추가, 다운로드 버그 수정
- NasPublisher: DSM 7.2 multipart body 수동 구성으로 업로드 401 오류 해결 - NasPublisher: 비밀번호 StreamingAssets/nas_config.json 분리, .gitignore 등록 - NasPublisher: staticBaseUrl 포트 8180 → 80 수정 - BeatSageUploader: Easy 난이도 제거, ExpertPlus(.dat) 추가 - NoteData: DifficultyMap에서 easy 제거, expertplus 추가 - SongCreatorManager: toggleEasy → toggleExpertPlus 교체 - SongDetailPanel: btnEasy → btnExpertPlus 교체 - DownloadManager: DownloadHandlerFile 경로 정규화(Path.GetFullPath), mapFile 빈 값 방어 처리 - PersistentXRRig: FindObjectsOfType obsolete 경고 수정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,15 +10,36 @@ using UnityEngine.Networking;
|
||||
public class NasPublisher : MonoBehaviour
|
||||
{
|
||||
[Header("NAS 접속 정보")]
|
||||
[SerializeField] private string nasBaseUrl = "http://192.168.55.3:5000"; // DSM 포트 (내부망)
|
||||
[SerializeField] private string nasBaseUrl = "http://192.168.55.3:5000";
|
||||
[SerializeField] private string nasAccount = "admin";
|
||||
[SerializeField] private string nasPassword = ""; // Inspector에서 입력
|
||||
[SerializeField] private string nasRootPath = "/web/beatsaber"; // NAS 내 절대경로
|
||||
[SerializeField] private string nasRootPath = "/web/beatsaber";
|
||||
|
||||
[Header("정적 서버 URL (songs.json 읽기용)")]
|
||||
[SerializeField] private string staticBaseUrl = "http://whdwo798.synology.me:8180/beatsaber";
|
||||
[SerializeField] private string staticBaseUrl = "http://whdwo798.synology.me/beatsaber";
|
||||
|
||||
private string _sid = ""; // DSM 세션 ID
|
||||
private string _sid = "";
|
||||
private string _synoToken = ""; // DSM 7 CSRF 토큰 (enable_syno_token=yes 로 획득)
|
||||
private string _password = ""; // StreamingAssets/nas_config.json 에서 로드
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
private void LoadConfig()
|
||||
{
|
||||
string path = Path.Combine(Application.streamingAssetsPath, "nas_config.json");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Debug.LogWarning("[NasPublisher] nas_config.json 없음: " + path);
|
||||
return;
|
||||
}
|
||||
var cfg = JsonUtility.FromJson<NasConfig>(File.ReadAllText(path));
|
||||
_password = cfg?.password ?? "";
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class NasConfig { public string password; }
|
||||
|
||||
// ── Public API ───────────────────────────────────────────
|
||||
|
||||
@@ -30,17 +51,17 @@ public class NasPublisher : MonoBehaviour
|
||||
Action onComplete,
|
||||
Action<string> onError)
|
||||
{
|
||||
bool failed = false;
|
||||
void OnErr(string e) { onError?.Invoke(e); failed = true; }
|
||||
|
||||
// 1단계: DSM 로그인
|
||||
yield return Login(onError);
|
||||
yield return Login(OnErr);
|
||||
if (string.IsNullOrEmpty(_sid)) yield break;
|
||||
onProgress?.Invoke(0.1f);
|
||||
|
||||
// 2단계: 오디오 업로드
|
||||
yield return UploadFile(
|
||||
audioPath,
|
||||
$"{nasRootPath}/music",
|
||||
$"{song.id}.mp3",
|
||||
onError);
|
||||
yield return UploadFile(audioPath, $"{nasRootPath}/music", $"{song.id}.mp3", OnErr);
|
||||
if (failed) { yield return Logout(); yield break; }
|
||||
onProgress?.Invoke(0.4f);
|
||||
|
||||
// 3단계: 각 난이도 맵 JSON 업로드
|
||||
@@ -53,20 +74,18 @@ public class NasPublisher : MonoBehaviour
|
||||
string json = BeatSageConverter.ToMapJson(kv.Value);
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
// 파일명에 맞춰 DifficultyInfo 업데이트
|
||||
AssignMapFile(song, kv.Key, fileName);
|
||||
|
||||
yield return UploadBytes(
|
||||
bytes, fileName,
|
||||
$"{nasRootPath}/maps",
|
||||
onError);
|
||||
yield return UploadBytes(bytes, fileName, $"{nasRootPath}/maps", OnErr);
|
||||
if (failed) { yield return Logout(); yield break; }
|
||||
|
||||
done++;
|
||||
onProgress?.Invoke(0.4f + (float)done / total * 0.3f);
|
||||
}
|
||||
|
||||
// 4단계: songs.json 다운로드 → 항목 추가 → 재업로드
|
||||
yield return PatchSongsJson(song, onError);
|
||||
yield return PatchSongsJson(song, OnErr);
|
||||
if (failed) { yield return Logout(); yield break; }
|
||||
onProgress?.Invoke(0.95f);
|
||||
|
||||
// 5단계: 로그아웃
|
||||
@@ -81,11 +100,12 @@ public class NasPublisher : MonoBehaviour
|
||||
|
||||
private IEnumerator Login(Action<string> onError)
|
||||
{
|
||||
Debug.Log($"[NasPublisher] 로그인 시도 — nasBaseUrl: '{nasBaseUrl}'");
|
||||
string url = $"{nasBaseUrl}/webapi/auth.cgi" +
|
||||
$"?api=SYNO.API.Auth&version=3&method=login" +
|
||||
$"?api=SYNO.API.Auth&version=6&method=login" +
|
||||
$"&account={UnityWebRequest.EscapeURL(nasAccount)}" +
|
||||
$"&passwd={UnityWebRequest.EscapeURL(nasPassword)}" +
|
||||
$"&session=FileStation&format=sid";
|
||||
$"&passwd={UnityWebRequest.EscapeURL(_password)}" +
|
||||
$"&session=FileStation&format=sid&enable_syno_token=yes";
|
||||
|
||||
using var req = UnityWebRequest.Get(url);
|
||||
yield return req.SendWebRequest();
|
||||
@@ -96,7 +116,11 @@ public class NasPublisher : MonoBehaviour
|
||||
yield break;
|
||||
}
|
||||
|
||||
_sid = ParseSid(req.downloadHandler.text);
|
||||
Debug.Log($"[NasPublisher] 로그인 응답: {req.downloadHandler.text}");
|
||||
string resp = req.downloadHandler.text;
|
||||
_sid = ParseJsonString(resp, "sid");
|
||||
_synoToken = ParseJsonString(resp, "synotoken");
|
||||
Debug.Log($"[NasPublisher] sid={_sid}, synotoken={_synoToken}");
|
||||
if (string.IsNullOrEmpty(_sid))
|
||||
onError?.Invoke("DSM sid 파싱 실패 — 계정 정보를 확인하세요");
|
||||
}
|
||||
@@ -122,26 +146,61 @@ public class NasPublisher : MonoBehaviour
|
||||
private IEnumerator UploadBytes(byte[] bytes, string fileName,
|
||||
string nasFolder, Action<string> onError)
|
||||
{
|
||||
string url = $"{nasBaseUrl}/webapi/entry.cgi";
|
||||
Debug.Log($"[NasPublisher] 업로드 시도 — path: '{nasFolder}', file: '{fileName}'");
|
||||
|
||||
var form = new List<IMultipartFormSection>
|
||||
string uploadUrl = $"{nasBaseUrl}/webapi/entry.cgi" +
|
||||
$"?api=SYNO.FileStation.Upload&version=2&method=upload" +
|
||||
$"&_sid={UnityWebRequest.EscapeURL(_sid)}";
|
||||
|
||||
// PowerShell 테스트와 동일한 방식으로 multipart body 수동 구성
|
||||
string boundary = System.Guid.NewGuid().ToString("N");
|
||||
const string CRLF = "\r\n";
|
||||
|
||||
using var bodyStream = new MemoryStream();
|
||||
|
||||
void WriteText(string text)
|
||||
{
|
||||
new MultipartFormDataSection("api", "SYNO.FileStation.Upload"),
|
||||
new MultipartFormDataSection("version", "2"),
|
||||
new MultipartFormDataSection("method", "upload"),
|
||||
new MultipartFormDataSection("path", nasFolder),
|
||||
new MultipartFormDataSection("overwrite", "true"),
|
||||
new MultipartFormDataSection("_sid", _sid),
|
||||
new MultipartFormFileSection("file", bytes, fileName, "application/octet-stream"),
|
||||
};
|
||||
var b = Encoding.UTF8.GetBytes(text);
|
||||
bodyStream.Write(b, 0, b.Length);
|
||||
}
|
||||
void WriteField(string name, string value)
|
||||
{
|
||||
WriteText($"--{boundary}{CRLF}");
|
||||
WriteText($"Content-Disposition: form-data; name=\"{name}\"{CRLF}{CRLF}");
|
||||
WriteText(value);
|
||||
WriteText(CRLF);
|
||||
}
|
||||
|
||||
WriteField("path", nasFolder);
|
||||
WriteField("create_parents", "true");
|
||||
WriteField("overwrite", "true");
|
||||
WriteText($"--{boundary}{CRLF}");
|
||||
WriteText($"Content-Disposition: form-data; name=\"file\"; filename=\"{fileName}\"{CRLF}");
|
||||
WriteText($"Content-Type: application/octet-stream{CRLF}{CRLF}");
|
||||
bodyStream.Write(bytes, 0, bytes.Length);
|
||||
WriteText(CRLF);
|
||||
WriteText($"--{boundary}--{CRLF}");
|
||||
|
||||
byte[] bodyBytes = bodyStream.ToArray();
|
||||
|
||||
using var req = new UnityWebRequest(uploadUrl, "POST");
|
||||
req.uploadHandler = new UploadHandlerRaw(bodyBytes);
|
||||
req.downloadHandler = new DownloadHandlerBuffer();
|
||||
req.SetRequestHeader("Content-Type", $"multipart/form-data; boundary={boundary}");
|
||||
if (!string.IsNullOrEmpty(_synoToken))
|
||||
req.SetRequestHeader("X-SYNO-TOKEN", _synoToken);
|
||||
|
||||
using var req = UnityWebRequest.Post(url, form);
|
||||
yield return req.SendWebRequest();
|
||||
|
||||
if (req.result != UnityWebRequest.Result.Success)
|
||||
{
|
||||
onError?.Invoke($"업로드 실패({fileName}): {req.error}");
|
||||
else
|
||||
Debug.Log($"[NasPublisher] 업로드 완료: {fileName}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
Debug.Log($"[NasPublisher] 업로드 응답({fileName}): {req.downloadHandler.text}");
|
||||
if (req.downloadHandler.text.Contains("\"success\":false"))
|
||||
onError?.Invoke($"업로드 거부({fileName}): {req.downloadHandler.text}");
|
||||
}
|
||||
|
||||
// ── songs.json 패치 ───────────────────────────────────────
|
||||
@@ -178,12 +237,12 @@ public class NasPublisher : MonoBehaviour
|
||||
|
||||
// ── 유틸 ─────────────────────────────────────────────────
|
||||
|
||||
private static string ParseSid(string json)
|
||||
private static string ParseJsonString(string json, string key)
|
||||
{
|
||||
const string key = "\"sid\":\"";
|
||||
int start = json.IndexOf(key, StringComparison.Ordinal);
|
||||
string search = $"\"{key}\":\"";
|
||||
int start = json.IndexOf(search, StringComparison.Ordinal);
|
||||
if (start < 0) return null;
|
||||
start += key.Length;
|
||||
start += search.Length;
|
||||
int end = json.IndexOf('"', start);
|
||||
return end > start ? json.Substring(start, end - start) : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user