Debug update a playlist
This commit is contained in:
parent
2ad7b50f65
commit
7301c10aa9
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BeatSaberPlaylistsLib.Types;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
#if false
|
#if false
|
||||||
using BeatLeader.Utils;
|
using BeatLeader.Utils;
|
||||||
@ -71,7 +72,7 @@ namespace Setlist
|
|||||||
/// ownership from playlist JSON. Must be called from the Unity main thread (BSIPA <c>OnApplicationStart</c> is fine).
|
/// ownership from playlist JSON. Must be called from the Unity main thread (BSIPA <c>OnApplicationStart</c> is fine).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static void ScheduleVerifyAndLog(
|
internal static void ScheduleVerifyAndLog(
|
||||||
List<(string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)> entries,
|
List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)> entries,
|
||||||
IPALogger log)
|
IPALogger log)
|
||||||
{
|
{
|
||||||
var go = new GameObject("Setlist.OwnershipRunner");
|
var go = new GameObject("Setlist.OwnershipRunner");
|
||||||
@ -86,11 +87,11 @@ namespace Setlist
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private sealed class OwnershipRunner : MonoBehaviour
|
private sealed class OwnershipRunner : MonoBehaviour
|
||||||
{
|
{
|
||||||
private List<(string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)> _entries;
|
private List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)> _entries;
|
||||||
private IPALogger _log;
|
private IPALogger _log;
|
||||||
|
|
||||||
public void Initialize(
|
public void Initialize(
|
||||||
List<(string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)> entries,
|
List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)> entries,
|
||||||
IPALogger log)
|
IPALogger log)
|
||||||
{
|
{
|
||||||
_entries = entries;
|
_entries = entries;
|
||||||
@ -116,6 +117,23 @@ namespace Setlist
|
|||||||
platformUserId));
|
platformUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var e in _entries)
|
||||||
|
{
|
||||||
|
if (!string.Equals(e.Title, BeatLeaderPlaylistUpdateTest.TargetPlaylistTitle, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return BeatLeaderPlaylistUpdateTest.RunIfTargetOwned(
|
||||||
|
e.Playlist,
|
||||||
|
e.HasSyncUrl,
|
||||||
|
e.BeatLeaderGuid,
|
||||||
|
e.OwnerId,
|
||||||
|
platformUserId,
|
||||||
|
_log);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
244
Setlist/BeatLeaderPlaylistUpdateTest.cs
Normal file
244
Setlist/BeatLeaderPlaylistUpdateTest.cs
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using BeatLeader.Utils;
|
||||||
|
using BeatSaberPlaylistsLib;
|
||||||
|
using BeatSaberPlaylistsLib.Types;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Networking;
|
||||||
|
using IPALogger = IPA.Logging.Logger;
|
||||||
|
|
||||||
|
namespace Setlist
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Dev/test path: POST the on-disk playlist JSON to BeatLeader to prove authenticated updates work.
|
||||||
|
/// Requires BeatLeader to have finished sign-in (Unity cookie cache). See docs/beatleader-playlist-api.md.
|
||||||
|
/// </summary>
|
||||||
|
internal static class BeatLeaderPlaylistUpdateTest
|
||||||
|
{
|
||||||
|
/// <summary>When this playlist title is locally owned (owner matches platform id), run POST test on startup.</summary>
|
||||||
|
internal const string TargetPlaylistTitle = "2025-mapfilter-01";
|
||||||
|
|
||||||
|
private const string AuthenticationTypeName = "BeatLeader.API.Authentication";
|
||||||
|
private const string SignedInFieldName = "_signedIn";
|
||||||
|
private const float SignInWaitTimeoutSeconds = 90f;
|
||||||
|
private const int PostRequestTimeoutSeconds = 120;
|
||||||
|
|
||||||
|
/// <summary>POST <c>/user/playlist</c> with the serialized playlist body (same shape as the website sample).</summary>
|
||||||
|
internal static IEnumerator RunIfTargetOwned(
|
||||||
|
IPlaylist playlist,
|
||||||
|
bool hasSyncUrl,
|
||||||
|
string beatLeaderGuid,
|
||||||
|
string ownerId,
|
||||||
|
string platformUserId,
|
||||||
|
IPALogger log)
|
||||||
|
{
|
||||||
|
if (!string.Equals(playlist.Title, TargetPlaylistTitle, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsLocallyOwnedBeatLeaderPlaylist(hasSyncUrl, beatLeaderGuid, ownerId, platformUserId))
|
||||||
|
{
|
||||||
|
log.Info(
|
||||||
|
$"Setlist BeatLeader POST test: skip \"{TargetPlaylistTitle}\" (not locally owned or missing BeatLeader fields).");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TryGetBeatLeaderServerFields(playlist, out var serverPlaylistId, out var shared))
|
||||||
|
{
|
||||||
|
log.Info(
|
||||||
|
$"Setlist BeatLeader POST test: skip \"{TargetPlaylistTitle}\" (customData missing id or not parseable).");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var signedInField = TryResolveBeatLeaderSignedInField(log);
|
||||||
|
if (signedInField == null)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var waited = 0f;
|
||||||
|
while (!IsSignedIn(signedInField))
|
||||||
|
{
|
||||||
|
if (waited >= SignInWaitTimeoutSeconds)
|
||||||
|
{
|
||||||
|
log.Info(
|
||||||
|
$"Setlist BeatLeader POST test: BeatLeader login did not complete within {SignInWaitTimeoutSeconds:F0}s.");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return new WaitForSeconds(1f);
|
||||||
|
waited += 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TrySerializePlaylist(playlist, log, out var bodyJson))
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url =
|
||||||
|
$"{BLConstants.BEATLEADER_API_URL}/user/playlist?id={Uri.EscapeDataString(serverPlaylistId)}&shared="
|
||||||
|
+ (shared ? "true" : "false");
|
||||||
|
|
||||||
|
byte[] bodyRaw = Encoding.UTF8.GetBytes(bodyJson);
|
||||||
|
using (var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST))
|
||||||
|
{
|
||||||
|
request.timeout = PostRequestTimeoutSeconds;
|
||||||
|
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
|
||||||
|
request.downloadHandler = new DownloadHandlerBuffer();
|
||||||
|
request.SetRequestHeader("Content-Type", "text/plain;charset=UTF-8");
|
||||||
|
request.SetRequestHeader("User-Agent", "Setlist/" + GetAssemblyVersion());
|
||||||
|
|
||||||
|
yield return request.SendWebRequest();
|
||||||
|
|
||||||
|
if (request.result == UnityWebRequest.Result.ConnectionError)
|
||||||
|
{
|
||||||
|
log.Info("Setlist BeatLeader POST test: network error: " + request.error);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.result == UnityWebRequest.Result.ProtocolError)
|
||||||
|
{
|
||||||
|
log.Info(
|
||||||
|
$"Setlist BeatLeader POST test: HTTP {request.responseCode} body="
|
||||||
|
+ TruncateForLog(request.downloadHandler?.text));
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(
|
||||||
|
"Setlist BeatLeader POST test: success HTTP "
|
||||||
|
+ request.responseCode
|
||||||
|
+ " body="
|
||||||
|
+ TruncateForLog(request.downloadHandler?.text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsLocallyOwnedBeatLeaderPlaylist(
|
||||||
|
bool hasSyncUrl,
|
||||||
|
string beatLeaderGuid,
|
||||||
|
string ownerId,
|
||||||
|
string platformUserId)
|
||||||
|
{
|
||||||
|
return hasSyncUrl
|
||||||
|
&& !string.IsNullOrEmpty(beatLeaderGuid)
|
||||||
|
&& !string.IsNullOrEmpty(ownerId)
|
||||||
|
&& !string.IsNullOrEmpty(platformUserId)
|
||||||
|
&& string.Equals(ownerId, platformUserId, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetBeatLeaderServerFields(IPlaylist playlist, out string serverPlaylistId, out bool shared)
|
||||||
|
{
|
||||||
|
serverPlaylistId = null;
|
||||||
|
shared = false;
|
||||||
|
|
||||||
|
if (!playlist.TryGetCustomData("id", out var idObj) || idObj == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
serverPlaylistId = Convert.ToString(idObj);
|
||||||
|
if (string.IsNullOrWhiteSpace(serverPlaylistId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playlist.TryGetCustomData("shared", out var sharedObj) && sharedObj != null)
|
||||||
|
{
|
||||||
|
if (sharedObj is bool b)
|
||||||
|
{
|
||||||
|
shared = b;
|
||||||
|
}
|
||||||
|
else if (bool.TryParse(Convert.ToString(sharedObj), out var parsed))
|
||||||
|
{
|
||||||
|
shared = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TrySerializePlaylist(IPlaylist playlist, IPALogger log, out string json)
|
||||||
|
{
|
||||||
|
json = null;
|
||||||
|
var mgr = PlaylistManager.DefaultManager;
|
||||||
|
IPlaylistHandler handler = null;
|
||||||
|
if (!string.IsNullOrEmpty(playlist.SuggestedExtension))
|
||||||
|
{
|
||||||
|
handler = mgr.GetHandlerForExtension(playlist.SuggestedExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler == null)
|
||||||
|
{
|
||||||
|
handler = mgr.GetHandlerForPlaylistType(playlist.GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler == null)
|
||||||
|
{
|
||||||
|
log.Info("Setlist BeatLeader POST test: no IPlaylistHandler for playlist type.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
handler.Serialize(playlist, stream);
|
||||||
|
json = Encoding.UTF8.GetString(stream.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
return !string.IsNullOrEmpty(json);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
log.Info("Setlist BeatLeader POST test: serialize failed: " + ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FieldInfo TryResolveBeatLeaderSignedInField(IPALogger log)
|
||||||
|
{
|
||||||
|
var beatLeaderAssembly = typeof(BLConstants).Assembly;
|
||||||
|
var authType = beatLeaderAssembly.GetType(AuthenticationTypeName);
|
||||||
|
if (authType == null)
|
||||||
|
{
|
||||||
|
log.Info($"Setlist BeatLeader POST test: assembly has no {AuthenticationTypeName}.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var field = authType.GetField(SignedInFieldName, BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
log.Info(
|
||||||
|
$"{AuthenticationTypeName} has no static field '{SignedInFieldName}' (BeatLeader API changed?).");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSignedIn(FieldInfo signedInField)
|
||||||
|
{
|
||||||
|
var value = signedInField.GetValue(null);
|
||||||
|
return value is bool b && b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAssemblyVersion()
|
||||||
|
{
|
||||||
|
var asm = typeof(BeatLeaderPlaylistUpdateTest).Assembly;
|
||||||
|
return asm.GetName().Version?.ToString() ?? "0.0.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TruncateForLog(string s, int maxLen = 512)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(s))
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.Length <= maxLen ? s : s.Substring(0, maxLen) + "…";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using BeatSaberPlaylistsLib.Types;
|
||||||
using IPA;
|
using IPA;
|
||||||
using IPALogger = IPA.Logging.Logger;
|
using IPALogger = IPA.Logging.Logger;
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ namespace Setlist
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entries = new List<(string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)>();
|
var entries = new List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)>();
|
||||||
foreach (var playlist in playlists)
|
foreach (var playlist in playlists)
|
||||||
{
|
{
|
||||||
var hasSyncUrl = false;
|
var hasSyncUrl = false;
|
||||||
@ -69,7 +70,7 @@ namespace Setlist
|
|||||||
blGuid = g;
|
blGuid = g;
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.Add((playlist.Title, hasSyncUrl, blGuid, ownerId));
|
entries.Add((playlist, playlist.Title, hasSyncUrl, blGuid, ownerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
BeatLeaderPlaylistOwnership.ScheduleVerifyAndLog(entries, Log);
|
BeatLeaderPlaylistOwnership.ScheduleVerifyAndLog(entries, Log);
|
||||||
|
|||||||
@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
[assembly: Guid("50F53E6E-21D5-4780-8E67-273877DAA28C")]
|
[assembly: Guid("50F53E6E-21D5-4780-8E67-273877DAA28C")]
|
||||||
[assembly: AssemblyVersion("0.0.6.0")]
|
[assembly: AssemblyVersion("0.0.7.0")]
|
||||||
[assembly: AssemblyFileVersion("0.0.6.0")]
|
[assembly: AssemblyFileVersion("0.0.7.0")]
|
||||||
|
|||||||
@ -118,6 +118,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="BeatLeaderPlaylistOwnership.cs" />
|
<Compile Include="BeatLeaderPlaylistOwnership.cs" />
|
||||||
|
<Compile Include="BeatLeaderPlaylistUpdateTest.cs" />
|
||||||
<Compile Include="Plugin.cs" />
|
<Compile Include="Plugin.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"id": "Setlist",
|
"id": "Setlist",
|
||||||
"name": "Setlist",
|
"name": "Setlist",
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "0.0.6",
|
"version": "0.0.7",
|
||||||
"description": "Syncs playlists with external sources.",
|
"description": "Syncs playlists with external sources.",
|
||||||
"gameVersion": "1.40.8",
|
"gameVersion": "1.40.8",
|
||||||
"dependsOn": {
|
"dependsOn": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user