Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
cadon committed Feb 4, 2024
2 parents 8e96036 + 9d9844e commit bd979b3
Show file tree
Hide file tree
Showing 52 changed files with 1,219 additions and 651 deletions.
7 changes: 7 additions & 0 deletions ARKBreedingStats/ARKBreedingStats.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
</Compile>
<Compile Include="Ark.cs" />
<Compile Include="AsbServer\Connection.cs" />
<Compile Include="AsbServer\ProgressReportAsbServer.cs" />
<Compile Include="BreedingPlanning\Score.cs" />
<Compile Include="BreedingPlanning\BreedingScore.cs" />
<Compile Include="importExportGun\ExportGunCreatureFile.cs" />
Expand All @@ -98,6 +99,7 @@
</Compile>
<Compile Include="library\CreatureSpawnCommand.cs" />
<Compile Include="library\DummyCreatures.cs" />
<Compile Include="library\LevelStatusFlags.cs" />
<Compile Include="multiplierTesting\CalculateMultipliers.cs" />
<Compile Include="NamePatterns\NameList.cs" />
<Compile Include="NamePatterns\NamePatternEntry.cs">
Expand Down Expand Up @@ -130,6 +132,9 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="uiControls\LibraryInfo.cs" />
<Compile Include="uiControls\PopupMessage.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="uiControls\StatSelector.cs">
<SubType>UserControl</SubType>
</Compile>
Expand Down Expand Up @@ -676,6 +681,8 @@
<Content Include="json\creatureNamesU.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="Resources\updated.wav" />
<None Include="Resources\newMutation.wav" />
<Content Include="_manifest.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>_manifest.json</LastGenOutput>
Expand Down
6 changes: 6 additions & 0 deletions ARKBreedingStats/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,12 @@
<setting name="LibraryShowMutationLevelColumns" serializeAs="String">
<value>True</value>
</setting>
<setting name="StreamerMode" serializeAs="String">
<value>False</value>
</setting>
<setting name="LibraryDisplayZeroMutationLevels" serializeAs="String">
<value>False</value>
</setting>
</ARKBreedingStats.Properties.Settings>
</userSettings>
</configuration>
18 changes: 17 additions & 1 deletion ARKBreedingStats/Ark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,26 @@ public static class Ark
public const byte ColorFirstId = 1;
public const byte DyeFirstIdASE = 201;
public const byte DyeMaxId = 255;

/// <summary>
/// When choosing a random color for a mutation, ARK can erroneously select an undefined color. For ASE that's the color id 227 (one too high to be defined).
/// </summary>
public const byte UndefinedColorIdAse = 227;

/// <summary>
/// When choosing a random color for a mutation, ARK can erroneously select an undefined color. For ASA that's the color id 255 (one too high to be defined).
/// </summary>
public const byte UndefinedColorIdAsa = 255;

/// <summary>
/// When choosing a random color for a mutation, ARK can erroneously select an undefined color. Usually this is color id 227 (one too high to be defined).
/// </summary>
public const byte UndefinedColorId = 227;
public static byte UndefinedColorId = UndefinedColorIdAse;

/// <summary>
/// Sets the undefined color id to the one of ASE or ASA.
/// </summary>
public static void SetUndefinedColorId(bool asa) => UndefinedColorId = asa ? UndefinedColorIdAsa : UndefinedColorIdAse;

/// <summary>
/// Number of possible color regions for all species.
Expand Down
193 changes: 118 additions & 75 deletions ARKBreedingStats/AsbServer/Connection.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using ARKBreedingStats.importExportGun;
using ARKBreedingStats.Library;
using Newtonsoft.Json.Linq;
Expand All @@ -21,83 +19,115 @@ internal static class Connection
{
private const string ApiUri = "https://export.arkbreeder.com/api/v1/";

private static SimpleCancellationToken _lastCancellationToken;
private static CancellationTokenSource _lastCancellationTokenSource;

public static async void StartListeningAsync(
IProgress<(string jsonText, string serverHash, string message)> progressDataSent, string token = null)
IProgress<ProgressReportAsbServer> progressDataSent, string token = null)
{
if (string.IsNullOrEmpty(token)) return;

// stop previous listening if any
StopListening();
var cancellationToken = new SimpleCancellationToken();
_lastCancellationToken = cancellationToken;

var requestUri = ApiUri + "listen/" + token; // "https://httpstat.us/429";

try
using (var cancellationTokenSource = new CancellationTokenSource())
{
var client = FileService.GetHttpClient;
using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead))
_lastCancellationTokenSource = cancellationTokenSource;

var reconnectTries = 0;

while (!cancellationTokenSource.Token.IsCancellationRequested)
{
if (!response.IsSuccessStatusCode)
{
var statusCode = (int)response.StatusCode;
string serverMessage = await response.Content.ReadAsStringAsync();
var requestUri = ApiUri + "listen/" + token; // "https://httpstat.us/429";

try
{
serverMessage = JObject.Parse(serverMessage).SelectToken("error.message").ToString();
}
catch
try
{
var client = FileService.GetHttpClient;
using (var response = await client.GetAsync(requestUri, HttpCompletionOption.ResponseHeadersRead))
{
// server message in unknown format, use raw content string
}
if (!response.IsSuccessStatusCode)
{
var statusCode = (int)response.StatusCode;
string serverMessage = await response.Content.ReadAsStringAsync();

serverMessage = Environment.NewLine + serverMessage;
try
{
serverMessage = JObject.Parse(serverMessage).SelectToken("error.message").ToString();
}
catch
{
// server message in unknown format, use raw content string
}

switch (statusCode)
{
case 400: // Bad Request
WriteMessage($"Something went wrong with the server connection.{serverMessage}", response);
return;
case 429: // Too Many Requests
WriteMessage($"The server is currently at the rate limit and cannot process the request. Try again later.{serverMessage}", response);
return;
case 507: // Insufficient Storage
WriteMessage($"Too many connections to the server. Try again later.{serverMessage}", response);
return;
default:
var errorMessage = statusCode >= 500
? "Something went wrong with the server or its proxy." + Environment.NewLine
: null;
WriteMessage($"{errorMessage}{serverMessage}", response);
return;
serverMessage = Environment.NewLine + serverMessage;

switch (statusCode)
{
case 400: // Bad Request
WriteErrorMessage($"Something went wrong with the server connection.{serverMessage}",
response);
return;
case 429: // Too Many Requests
WriteErrorMessage(
$"The server is currently at the rate limit and cannot process the request. Try again later.{serverMessage}",
response);
return;
case 507: // Insufficient Storage
WriteErrorMessage($"Too many connections to the server. Try again later.{serverMessage}",
response);
return;
default:
var errorMessage = statusCode >= 500
? "Something went wrong with the server or its proxy." + Environment.NewLine
: null;
WriteErrorMessage($"{errorMessage}{serverMessage}", response);
return;
}
}

reconnectTries = 0;

using (var stream = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream))
{
var report = await ReadServerSentEvents(reader, progressDataSent, token, cancellationTokenSource.Token);
if (report != null)
{
progressDataSent.Report(report);
if (report.StopListening)
{
StopListening();
}
}
}
}
}

using (var stream = await response.Content.ReadAsStreamAsync())
using (var reader = new StreamReader(stream))
catch (Exception ex)
{
await ReadServerSentEvents(reader, progressDataSent, token, cancellationToken);
var tryToReconnect = reconnectTries++ < 3;
WriteErrorMessage(
$"ASB Server listening {ex.GetType()}: {ex.Message}{Environment.NewLine}{(tryToReconnect ? "Trying to reconnect" + Environment.NewLine : string.Empty)}Stack trace: {ex.StackTrace}",
stopListening: !tryToReconnect);

if (!tryToReconnect)
break;
// try to reconnect after some time
Thread.Sleep(10_000);
}
}
}
catch (Exception ex)
{
WriteMessage($"ASB Server listening exception:\n{ex.Message}\n\nStack trace:\n{ex.StackTrace}");
}
finally
{
finally
{
#if DEBUG
Console.WriteLine($"ASB Server listening stopped using token: {token}");
Console.WriteLine($"ASB Server listening stopped using token: {token}");
#endif
}
}
}

_lastCancellationTokenSource = null;

return;

// Displays an error message in the UI, also logs on the console if in debug mode
void WriteMessage(string message, HttpResponseMessage response = null)
void WriteErrorMessage(string message, HttpResponseMessage response = null, bool stopListening = true)
{
if (response != null)
{
Expand All @@ -106,17 +136,24 @@ void WriteMessage(string message, HttpResponseMessage response = null)
#if DEBUG
Console.WriteLine(message);
#endif
progressDataSent.Report((null, null, message));
progressDataSent.Report(new ProgressReportAsbServer { Message = message, StopListening = stopListening, IsError = true });
}
}

private static Regex _eventRegex = new Regex(@"^event: (welcome|ping|replaced|export|server|closing)(?: (\-?\d+))?(?:\ndata:\s(.+))?$");

private static async Task ReadServerSentEvents(StreamReader reader, IProgress<(string jsonText, string serverHash, string message)> progressDataSent, string token, SimpleCancellationToken cancellationToken)
private static async Task<ProgressReportAsbServer> ReadServerSentEvents(StreamReader reader, IProgress<ProgressReportAsbServer> progressDataSent, string token, CancellationToken cancellationToken)
{
#if DEBUG
Console.WriteLine($"Now listening using token: {token}");
#endif
progressDataSent.Report(new ProgressReportAsbServer
{
Message = "Now listening to the export server using the token (also copied to clipboard)",
ServerToken = token,
ClipboardText = token
});

while (!cancellationToken.IsCancellationRequested)
{
var received = await reader.ReadLineAsync();
Expand All @@ -133,16 +170,19 @@ private static async Task ReadServerSentEvents(StreamReader reader, IProgress<(s
case "event: ping":
continue;
case "event: replaced":
if (!cancellationToken.IsCancellationRequested)
progressDataSent.Report((null, null,
"ASB Server listening stopped. Connection used by a different user"));
return;
if (cancellationToken.IsCancellationRequested) return null;

return new ProgressReportAsbServer
{
Message = "ASB Server listening stopped. Connection used by a different user",
StopListening = true,
IsError = true
};
case "event: closing":
// only report closing if the user hasn't done this already
if (!cancellationToken.IsCancellationRequested)
progressDataSent.Report((null, null,
"ASB Server listening stopped. Connection closed by the server"));
return;
if (cancellationToken.IsCancellationRequested) return null;
return new ProgressReportAsbServer
{ Message = "ASB Server listening stopped. Connection closed by the server, trying to reconnect", StopListening = false, IsError = true };
}

if (received != "event: export" && !received.StartsWith("event: server"))
Expand All @@ -160,23 +200,28 @@ private static async Task ReadServerSentEvents(StreamReader reader, IProgress<(s
switch (m.Groups[1].Value)
{
case "export":
progressDataSent.Report((m.Groups[3].Value, null, null));
progressDataSent.Report(new ProgressReportAsbServer { JsonText = m.Groups[3].Value });
break;
case "server":
progressDataSent.Report((m.Groups[3].Value, m.Groups[2].Value, null));
progressDataSent.Report(new ProgressReportAsbServer { JsonText = m.Groups[3].Value, ServerHash = m.Groups[2].Value });
break;
}
}
}

return null;
}

/// <summary>
/// Stops the currently running listener. Returns false if no listener was running.
/// </summary>
public static void StopListening()
{
if (_lastCancellationToken == null)
if (_lastCancellationTokenSource == null)
return; // nothing to stop

_lastCancellationToken.Cancel();
_lastCancellationToken = null;
_lastCancellationTokenSource.Cancel();
_lastCancellationTokenSource = null;
}

/// <summary>
Expand Down Expand Up @@ -227,12 +272,10 @@ public static string CreateNewToken()
}

/// <summary>
/// Simple replacement of CancellationTokenSource to avoid unnecessary complexities with disposal of CTS when the token is still in use.
/// Returns the passed token string, or wildcards if the streamer mode is enabled.
/// </summary>
private class SimpleCancellationToken
{
public bool IsCancellationRequested;
public void Cancel() => IsCancellationRequested = true;
}
public static string TokenStringForDisplay(string token) => Properties.Settings.Default.StreamerMode ? "****" : token;

public static bool IsCurrentlyListening => _lastCancellationTokenSource != null;
}
}
16 changes: 16 additions & 0 deletions ARKBreedingStats/AsbServer/ProgressReportAsbServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace ARKBreedingStats.AsbServer
{
/// <summary>
/// Info of progress reports while listening to an AsbServer.
/// </summary>
internal class ProgressReportAsbServer
{
public string JsonText;
public string ServerHash;
public string Message;
public string ServerToken;
public bool IsError;
public bool StopListening;
public string ClipboardText;
}
}
Loading

0 comments on commit bd979b3

Please sign in to comment.