Skip to content

Commit

Permalink
Refactor MasterServerMP4
Browse files Browse the repository at this point in the history
  • Loading branch information
BigBang1112 committed Jan 16, 2025
1 parent 1d9649c commit 9dbdf3d
Showing 1 changed file with 118 additions and 138 deletions.
256 changes: 118 additions & 138 deletions Src/ManiaAPI.XmlRpc/MP4/MasterServerMP4.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ namespace ManiaAPI.XmlRpc.MP4;

public class MasterServerMP4 : MasterServer
{
private delegate T ProcessContent<T>(ref MiniXmlReader xml);

protected async Task<string> SendAsync(string titleId, string requestName, string parameters, CancellationToken cancellationToken)
{
var content = new StringContent(@$"
Expand Down Expand Up @@ -42,104 +44,50 @@ public async Task<IEnumerable<LeaderboardItem>> GetCampaignLeaderBoardAsync(
<t></t>
<z>{zone}</z>
<s>SkillPoint</s>", cancellationToken);
return ProcessResponseResult(RequestName, responseStr, ReadLeaderboardItems);
}

public async Task<IEnumerable<LeaderboardItem>> GetMapLeaderBoardAsync(
string titleId,
string mapUid,
int count = 10,
int offset = 0,
string zone = "World",
string context = "",
CancellationToken cancellationToken = default)
{
const string RequestName = "GetMapLeaderBoard";

var responseStr = await SendAsync(titleId, RequestName, @$"
<m>{mapUid}</m>
<n>{count}</n>
<f>{offset}</f>
<z>{zone}</z>
<c></c>
<t></t>
<s>MapRecord</s>", cancellationToken);
return ProcessResponseResult(RequestName, responseStr, ReadLeaderboardItems);
}

private static T ProcessResponseResult<T>(string requestName, string responseStr, ProcessContent<T> processContent) where T : notnull
{
var xml = new MiniXmlReader(responseStr);

xml.SkipProcessingInstruction();

if (!MemoryExtensions.Equals(xml.ReadStartElement(), "r", StringComparison.OrdinalIgnoreCase))
{
throw new Exception("<r> (request) not found");
throw new Exception("<r> (first one) not found");
}

var items = new List<LeaderboardItem>();
var content = default(T);

while (xml.TryReadStartElement(out var responseElement))
{
switch (responseElement)
{
case "r":
while (xml.TryReadStartElement(out var contentElement))
{
switch (contentElement)
{
case "n":
var requestName = xml.ReadContentAsString();
if (requestName != RequestName)
{
//throw new Exception("Invalid response"); invalid xml will respond with empty request name
}
break;
case "c":
while (xml.TryReadStartElement("i"))
{
var rank = 0;
var login = string.Empty;
var nickname = string.Empty;
var score = 0u;
var fileName = string.Empty;
var downloadUrl = string.Empty;

while (xml.TryReadStartElement(out var itemElement))
{
switch (itemElement)
{
case "r":
rank = int.Parse(xml.ReadContent());
break;
case "l":
login = xml.ReadContentAsString();
break;
case "n":
nickname = xml.ReadContentAsString();
break;
case "s":
score = uint.Parse(xml.ReadContent());
break;
case "f":
fileName = xml.ReadContentAsString();
break;
case "u":
downloadUrl = xml.ReadContentAsString();
break;
default:
xml.ReadContent();
break;
}

Debug.Assert(xml.SkipEndElement());
}

items.Add(new LeaderboardItem(rank, login, nickname, score, fileName, downloadUrl));

Debug.Assert(xml.SkipEndElement()); // i
}
break;
case "e":
var value = 0;
var message = string.Empty;

while (xml.TryReadStartElement(out var errorElement))
{
switch (errorElement)
{
case "v":
value = int.Parse(xml.ReadContent());
break;
case "m":
message = xml.ReadContentAsString();
break;
default:
xml.ReadContent();
break;
}

Debug.Assert(xml.SkipEndElement());
}

throw new Exception($"Error {value}: {message}");
}

Debug.Assert(xml.SkipEndElement()); // n, c or e
}

ProcessResponse(requestName, processContent, ref xml, out content);
break;
case "e":
var executionTime = xml.ReadContentAsString();
Expand All @@ -149,76 +97,108 @@ public async Task<IEnumerable<LeaderboardItem>> GetCampaignLeaderBoardAsync(
Debug.Assert(xml.SkipEndElement());
}

return items;
return content ?? throw new Exception("No response content");
}

public async Task<IEnumerable<LeaderboardItem>> GetMapLeaderBoardAsync(
string titleId,
string mapUid,
int count = 10,
int offset = 0,
string zone = "World",
string context = "",
CancellationToken cancellationToken = default)
private static void ProcessResponse<T>(string requestName, ProcessContent<T> processContent, ref MiniXmlReader xml, out T? content) where T : notnull
{
const string RequestName = "GetMapLeaderBoard";
content = default;

var responseStr = await SendAsync(titleId, RequestName, @$"
<m>{mapUid}</m>
<n>{count}</n>
<f>{offset}</f>
<z>{zone}</z>
<c></c>
<t></t>
<s>MapRecord</s>", cancellationToken);
while (xml.TryReadStartElement(out var contentElement))
{
switch (contentElement)
{
case "n":
var actualRequestName = xml.ReadContentAsString();
if (requestName != actualRequestName)
{
//throw new Exception("Invalid response"); invalid xml will respond with empty request name
}
break;
case "c":
content = processContent(ref xml);
break;
case "e":
ProcessErrorAndThrowException(xml);
return;
}

var xml = new MiniXmlReader(responseStr);
xml.SkipProcessingInstruction();
Debug.Assert(xml.SkipEndElement()); // n, c or e
}
}

var requestElement = xml.ReadStartElement();
var responseElement = xml.ReadStartElement();
var requestNameElement = xml.ReadStartElement();
var requestName = xml.ReadContentAsString();
private static void ProcessErrorAndThrowException(MiniXmlReader xml)
{
var value = 0;
var message = string.Empty;

if (requestName != RequestName)
while (xml.TryReadStartElement(out var errorElement))
{
throw new Exception("Invalid response");
switch (errorElement)
{
case "v":
value = int.Parse(xml.ReadContent());
break;
case "m":
message = xml.ReadContentAsString();
break;
default:
xml.ReadContent();
break;
}

Debug.Assert(xml.SkipEndElement());
}

xml.SkipEndElement();
var contentElement = xml.ReadStartElement();
throw new Exception($"Error {value}: {message}");
}

private static List<LeaderboardItem> ReadLeaderboardItems(ref MiniXmlReader xml)
{
var items = new List<LeaderboardItem>();

while (xml.TryReadStartElement("i"))
{
var rankElement = xml.ReadStartElement();
var rank = int.Parse(xml.ReadContent());
xml.SkipEndElement();

var loginElement = xml.ReadStartElement();
var login = xml.ReadContentAsString();
xml.SkipEndElement();

var nicknameElement = xml.ReadStartElement();
var nickname = xml.ReadContentAsString();
xml.SkipEndElement();

var scoreElement = xml.ReadStartElement();
var score = uint.Parse(xml.ReadContent());
xml.SkipEndElement();

var fileNameElement = xml.ReadStartElement();
var fileName = xml.ReadContentAsString();
xml.SkipEndElement();

var downloadUrlElement = xml.ReadStartElement();
var downloadUrl = xml.ReadContentAsString();
xml.SkipEndElement();
var rank = 0;
var login = string.Empty;
var nickname = string.Empty;
var score = 0u;
var fileName = string.Empty;
var downloadUrl = string.Empty;

while (xml.TryReadStartElement(out var itemElement))
{
switch (itemElement)
{
case "r":
rank = int.Parse(xml.ReadContent());
break;
case "l":
login = xml.ReadContentAsString();
break;
case "n":
nickname = xml.ReadContentAsString();
break;
case "s":
score = uint.Parse(xml.ReadContent());
break;
case "f":
fileName = xml.ReadContentAsString();
break;
case "u":
downloadUrl = xml.ReadContentAsString();
break;
default:
xml.ReadContent();
break;
}

Debug.Assert(xml.SkipEndElement());
}

items.Add(new LeaderboardItem(rank, login, nickname, score, fileName, downloadUrl));

xml.SkipEndElement();
Debug.Assert(xml.SkipEndElement()); // i
}

return items;
Expand Down

0 comments on commit 9dbdf3d

Please sign in to comment.