Replace WebClient with HttpClient

This commit is contained in:
teinarss
2021-02-21 16:56:55 +01:00
committed by Paul Chote
parent ed43071792
commit 7073279ab8
17 changed files with 604 additions and 562 deletions

View File

@@ -1,96 +0,0 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.ComponentModel;
using System.Net;
namespace OpenRA
{
public class Download
{
readonly object syncObject = new object();
WebClient wc;
public static string FormatErrorMessage(Exception e)
{
var ex = e as WebException;
if (ex == null)
return e.Message;
switch (ex.Status)
{
case WebExceptionStatus.RequestCanceled:
return "Cancelled";
case WebExceptionStatus.NameResolutionFailure:
return "DNS lookup failed";
case WebExceptionStatus.Timeout:
return "Connection timeout";
case WebExceptionStatus.ConnectFailure:
return "Cannot connect to remote server";
case WebExceptionStatus.ProtocolError:
return "File not found on remote server";
default:
return ex.Message;
}
}
void EnableTLS12OnWindows()
{
// Enable TLS 1.2 on Windows: .NET 4.7 on Windows 10 only supports obsolete protocols by default
// SecurityProtocolType.Tls12 is not defined in the .NET 4.5 reference dlls used by mono,
// so we must use the enum's constant value directly
if (Platform.CurrentPlatform == PlatformType.Windows)
ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072;
}
public Download(string url, string path, Action<DownloadProgressChangedEventArgs> onProgress, Action<AsyncCompletedEventArgs> onComplete)
{
EnableTLS12OnWindows();
lock (syncObject)
{
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadFileCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadFileAsync(new Uri(url), path);
}
}
public Download(string url, Action<DownloadProgressChangedEventArgs> onProgress, Action<DownloadDataCompletedEventArgs> onComplete)
{
EnableTLS12OnWindows();
lock (syncObject)
{
wc = new WebClient { Proxy = null };
wc.DownloadProgressChanged += (_, a) => onProgress(a);
wc.DownloadDataCompleted += (_, a) => { DisposeWebClient(); onComplete(a); };
wc.DownloadDataAsync(new Uri(url));
}
}
void DisposeWebClient()
{
lock (syncObject)
{
wc.Dispose();
wc = null;
}
}
public void CancelAsync()
{
lock (syncObject)
wc?.CancelAsync();
}
}
}

View File

@@ -0,0 +1,63 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace OpenRA
{
public delegate void OnProgress(long total, long totalRead, int progressPercentage);
public static class HttpExtension
{
public static async Task ReadAsStreamWithProgress(this HttpResponseMessage response, Stream outputStream, OnProgress onProgress, CancellationToken token)
{
var total = response.Content.Headers.ContentLength ?? -1;
var canReportProgress = total > 0;
#if !MONO
using (var contentStream = await response.Content.ReadAsStreamAsync(token))
#else
using (var contentStream = await response.Content.ReadAsStreamAsync())
#endif
{
var totalRead = 0L;
var buffer = new byte[8192];
var hasMoreToRead = true;
do
{
var read = await contentStream.ReadAsync(buffer.AsMemory(0, buffer.Length), token);
if (read == 0)
hasMoreToRead = false;
else
{
await outputStream.WriteAsync(buffer.AsMemory(0, read), token);
totalRead += read;
if (canReportProgress)
{
var progressPercentage = (int)((double)totalRead / total * 100);
onProgress?.Invoke(total, totalRead, progressPercentage);
}
}
}
while (hasMoreToRead && !token.IsCancellationRequested);
onProgress?.Invoke(total, totalRead, 100);
}
}
}
}

View File

@@ -12,10 +12,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenRA.Support;
namespace OpenRA namespace OpenRA
{ {
@@ -76,17 +76,16 @@ namespace OpenRA
if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed) if (State != LinkState.Unlinked && State != LinkState.Linked && State != LinkState.ConnectionFailed)
return; return;
Action<DownloadDataCompletedEventArgs> onQueryComplete = i => Task.Run(async () =>
{ {
try try
{ {
if (i.Error != null) var client = HttpClientFactory.Create();
{
innerState = LinkState.ConnectionFailed;
return;
}
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First(); var httpResponseMessage = await client.GetAsync(playerDatabase.Profile + Fingerprint);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
var yaml = MiniYaml.FromString(result).First();
if (yaml.Key == "Player") if (yaml.Key == "Player")
{ {
innerData = FieldLoader.Load<PlayerProfile>(yaml.Value); innerData = FieldLoader.Load<PlayerProfile>(yaml.Value);
@@ -110,10 +109,9 @@ namespace OpenRA
{ {
onComplete?.Invoke(); onComplete?.Invoke();
} }
}; });
innerState = LinkState.CheckingLink; innerState = LinkState.CheckingLink;
new Download(playerDatabase.Profile + Fingerprint, _ => { }, onQueryComplete);
} }
public void GenerateKeypair() public void GenerateKeypair()

View File

@@ -14,9 +14,8 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Primitives; using OpenRA.Primitives;
@@ -179,24 +178,16 @@ namespace OpenRA
var url = repositoryUrl + "hash/" + string.Join(",", maps.Keys) + "/yaml"; var url = repositoryUrl + "hash/" + string.Join(",", maps.Keys) + "/yaml";
Action<DownloadDataCompletedEventArgs> onInfoComplete = i => Task.Run(async () =>
{ {
if (i.Error != null)
{
Log.Write("debug", "Remote map query failed with error: {0}", Download.FormatErrorMessage(i.Error));
Log.Write("debug", "URL was: {0}", url);
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
queryFailed?.Invoke();
return;
}
var data = Encoding.UTF8.GetString(i.Result);
try try
{ {
var yaml = MiniYaml.FromString(data); var client = HttpClientFactory.Create();
var httpResponseMessage = await client.GetAsync(url);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
var yaml = MiniYaml.FromString(result);
foreach (var kv in yaml) foreach (var kv in yaml)
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived); maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value, mapDetailsReceived);
@@ -206,13 +197,15 @@ namespace OpenRA
} }
catch (Exception e) catch (Exception e)
{ {
Log.Write("debug", "Can't parse remote map search data:\n{0}", data); Log.Write("debug", "Remote map query failed with error: {0}", e);
Log.Write("debug", "Exception: {0}", e); Log.Write("debug", "URL was: {0}", url);
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
queryFailed?.Invoke(); queryFailed?.Invoke();
} }
}; });
new Download(url, _ => { }, onInfoComplete);
} }
void LoadAsyncInternal() void LoadAsyncInternal()

View File

@@ -14,13 +14,15 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.FileSystem; using OpenRA.FileSystem;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA namespace OpenRA
{ {
@@ -166,7 +168,6 @@ namespace OpenRA
} }
} }
Download download;
public long DownloadBytes { get; private set; } public long DownloadBytes { get; private set; }
public int DownloadPercentage { get; private set; } public int DownloadPercentage { get; private set; }
@@ -435,74 +436,57 @@ namespace OpenRA
} }
var mapInstallPackage = installLocation.Key as IReadWritePackage; var mapInstallPackage = installLocation.Key as IReadWritePackage;
new Thread(() =>
Task.Run(async () =>
{ {
// Request the filename from the server // Request the filename from the server
// Run in a worker thread to avoid network delays // Run in a worker thread to avoid network delays
var mapUrl = mapRepositoryUrl + Uid; var mapUrl = mapRepositoryUrl + Uid;
var mapFilename = string.Empty;
try try
{ {
var request = WebRequest.Create(mapUrl); void OnDownloadProgress(long total, long received, int percentage)
request.Method = "HEAD";
using (var res = request.GetResponse())
{ {
// Map not found DownloadBytes = total;
if (res.Headers["Content-Disposition"] == null) DownloadPercentage = percentage;
{
innerData.Status = MapStatus.DownloadError;
return;
}
mapFilename = res.Headers["Content-Disposition"].Replace("attachment; filename = ", "");
} }
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i => { DownloadBytes = i.BytesReceived; DownloadPercentage = i.ProgressPercentage; }; var client = HttpClientFactory.Create();
Action<DownloadDataCompletedEventArgs> onDownloadComplete = i =>
var response = await client.GetAsync(mapUrl, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode)
{ {
download = null; innerData.Status = MapStatus.DownloadError;
return;
}
if (i.Error != null) response.Headers.TryGetValues("Content-Disposition", out var values);
{ var mapFilename = values.First().Replace("attachment; filename = ", "");
Log.Write("debug", "Remote map download failed with error: {0}", Download.FormatErrorMessage(i.Error));
Log.Write("debug", "URL was: {0}", mapUrl);
var fileStream = new MemoryStream();
await response.ReadAsStreamWithProgress(fileStream, OnDownloadProgress, CancellationToken.None);
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
Game.RunAfterTick(() =>
{
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
if (package == null)
innerData.Status = MapStatus.DownloadError; innerData.Status = MapStatus.DownloadError;
return; else
}
mapInstallPackage.Update(mapFilename, i.Result);
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
Game.RunAfterTick(() =>
{ {
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles); UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType);
if (package == null) onSuccess();
innerData.Status = MapStatus.DownloadError; }
else });
{
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType);
onSuccess();
}
});
};
download = new Download(mapUrl, onDownloadProgress, onDownloadComplete);
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e.Message); Console.WriteLine(e.Message);
innerData.Status = MapStatus.DownloadError; innerData.Status = MapStatus.DownloadError;
} }
}).Start(); });
}
public void CancelInstall()
{
if (download == null)
return;
download.CancelAsync();
download = null;
} }
public void Invalidate() public void Invalidate()

View File

@@ -41,6 +41,7 @@
<PackageReference Include="SharpZipLib" Version="1.3.1" /> <PackageReference Include="SharpZipLib" Version="1.3.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<AdditionalFiles Include="../stylecop.json" /> <AdditionalFiles Include="../stylecop.json" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(Mono)' == ''"> <ItemGroup Condition="'$(Mono)' == ''">

View File

@@ -9,13 +9,12 @@
*/ */
#endregion #endregion
using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Threading.Tasks;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA namespace OpenRA
{ {
@@ -39,14 +38,16 @@ namespace OpenRA
var spriteSize = IconSize * density; var spriteSize = IconSize * density;
var sprite = sheetBuilder.Allocate(new Size(spriteSize, spriteSize), 1f / density); var sprite = sheetBuilder.Allocate(new Size(spriteSize, spriteSize), 1f / density);
Action<DownloadDataCompletedEventArgs> onComplete = i => Task.Run(async () =>
{ {
if (i.Error != null)
return;
try try
{ {
var icon = new Png(new MemoryStream(i.Result)); var client = HttpClientFactory.Create();
var httpResponseMessage = await client.GetAsync(url);
var result = await httpResponseMessage.Content.ReadAsStreamAsync();
var icon = new Png(result);
if (icon.Width == spriteSize && icon.Height == spriteSize) if (icon.Width == spriteSize && icon.Height == spriteSize)
{ {
Game.RunAfterTick(() => Game.RunAfterTick(() =>
@@ -57,9 +58,7 @@ namespace OpenRA
} }
} }
catch { } catch { }
}; });
new Download(url, _ => { }, onComplete);
return sprite; return sprite;
} }

View File

@@ -18,6 +18,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using OpenRA.FileFormats; using OpenRA.FileFormats;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Primitives; using OpenRA.Primitives;
@@ -560,53 +561,50 @@ namespace OpenRA.Server
{ {
waitingForAuthenticationCallback++; waitingForAuthenticationCallback++;
Action<DownloadDataCompletedEventArgs> onQueryComplete = i => Task.Run(async () =>
{ {
var httpClient = HttpClientFactory.Create();
var httpResponseMessage = await httpClient.GetAsync(playerDatabase.Profile + handshake.Fingerprint);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
PlayerProfile profile = null; PlayerProfile profile = null;
if (i.Error == null) try
{ {
try var yaml = MiniYaml.FromString(result).First();
if (yaml.Key == "Player")
{ {
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First(); profile = FieldLoader.Load<PlayerProfile>(yaml.Value);
if (yaml.Key == "Player")
{
profile = FieldLoader.Load<PlayerProfile>(yaml.Value);
var publicKey = Encoding.ASCII.GetString(Convert.FromBase64String(profile.PublicKey)); var publicKey = Encoding.ASCII.GetString(Convert.FromBase64String(profile.PublicKey));
var parameters = CryptoUtil.DecodePEMPublicKey(publicKey); var parameters = CryptoUtil.DecodePEMPublicKey(publicKey);
if (!profile.KeyRevoked && CryptoUtil.VerifySignature(parameters, newConn.AuthToken, handshake.AuthSignature)) if (!profile.KeyRevoked && CryptoUtil.VerifySignature(parameters, newConn.AuthToken, handshake.AuthSignature))
{ {
client.Fingerprint = handshake.Fingerprint; client.Fingerprint = handshake.Fingerprint;
Log.Write("server", "{0} authenticated as {1} (UID {2})", newConn.Socket.RemoteEndPoint, Log.Write("server", "{0} authenticated as {1} (UID {2})", newConn.Socket.RemoteEndPoint,
profile.ProfileName, profile.ProfileID); profile.ProfileName, profile.ProfileID);
} }
else if (profile.KeyRevoked) else if (profile.KeyRevoked)
{ {
profile = null; profile = null;
Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint); Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
}
else
{
profile = null;
Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)",
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
}
} }
else else
Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)", {
newConn.Socket.RemoteEndPoint, handshake.Fingerprint, yaml.Key); profile = null;
} Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)",
catch (Exception ex) newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
{ }
Log.Write("server", "{0} failed to authenticate as {1} (exception occurred)",
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
Log.Write("server", ex.ToString());
} }
else
Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)",
newConn.Socket.RemoteEndPoint, handshake.Fingerprint, yaml.Key);
}
catch (Exception ex)
{
Log.Write("server", "{0} failed to authenticate as {1} (exception occurred)",
newConn.Socket.RemoteEndPoint, handshake.Fingerprint);
Log.Write("server", ex.ToString());
} }
else
Log.Write("server", "{0} failed to authenticate as {1} (server error: `{2}`)",
newConn.Socket.RemoteEndPoint, handshake.Fingerprint, i.Error);
delayedActions.Add(() => delayedActions.Add(() =>
{ {
@@ -636,9 +634,7 @@ namespace OpenRA.Server
waitingForAuthenticationCallback--; waitingForAuthenticationCallback--;
}, 0); }, 0);
}; });
new Download(playerDatabase.Profile + handshake.Fingerprint, _ => { }, onQueryComplete);
} }
else else
{ {

View File

@@ -0,0 +1,48 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Net.Http;
namespace OpenRA.Support
{
public class HttpClientFactory
{
#if !MONO
const int MaxConnectionPerServer = 20;
static readonly TimeSpan ConnectionLifeTime = TimeSpan.FromMinutes(1);
#endif
static readonly Lazy<HttpMessageHandler> Handler = new Lazy<HttpMessageHandler>(GetHandler);
public static HttpClient Create()
{
return new HttpClient(Handler.Value, false);
}
static HttpMessageHandler GetHandler()
{
#if !MONO
return new SocketsHttpHandler
{
// https://github.com/dotnet/corefx/issues/26895
// https://github.com/dotnet/corefx/issues/26331
// https://github.com/dotnet/corefx/pull/26839
PooledConnectionLifetime = ConnectionLifeTime,
PooledConnectionIdleTimeout = ConnectionLifeTime,
MaxConnectionsPerServer = MaxConnectionPerServer
};
#else
return new HttpClientHandler();
#endif
}
}
}

View File

@@ -0,0 +1,61 @@
#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace OpenRA.Support
{
public class HttpQueryBuilder : IEnumerable
{
readonly string url;
readonly List<Parameter> parameters = new List<Parameter>();
public HttpQueryBuilder(string url)
{
this.url = url;
}
public void Add(string name, object value)
{
parameters.Add(new Parameter
{
Name = name,
Value = Uri.EscapeUriString(value.ToString())
});
}
public override string ToString()
{
var builder = new StringBuilder(url);
builder.Append("?");
foreach (var parameter in parameters)
builder.Append($"{parameter.Name}={parameter.Value}&");
return builder.ToString();
}
class Parameter
{
public string Name { get; set; }
public string Value { get; set; }
}
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}
}

View File

@@ -33,6 +33,7 @@
<PackageReference Include="Pfim" Version="0.9.1" /> <PackageReference Include="Pfim" Version="0.9.1" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<AdditionalFiles Include="../stylecop.json" /> <AdditionalFiles Include="../stylecop.json" />
</ItemGroup> </ItemGroup>
<Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'"> <Target Name="DisableAnalyzers" BeforeTargets="CoreCompile" Condition="'$(Configuration)'=='Release'">

View File

@@ -11,12 +11,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net.Http;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using BeaconLib; using BeaconLib;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Server; using OpenRA.Server;
using OpenRA.Support;
using S = OpenRA.Server.Server; using S = OpenRA.Server.Server;
namespace OpenRA.Mods.Common.Server namespace OpenRA.Mods.Common.Server
@@ -41,7 +42,7 @@ namespace OpenRA.Mods.Common.Server
bool isInitialPing = true; bool isInitialPing = true;
volatile bool isBusy; volatile bool isBusy;
Queue<string> masterServerMessages = new Queue<string>(); readonly Queue<string> masterServerMessages = new Queue<string>();
static MasterServerPinger() static MasterServerPinger()
{ {
@@ -108,47 +109,47 @@ namespace OpenRA.Mods.Common.Server
{ {
isBusy = true; isBusy = true;
Task.Run(() => Task.Run(async () =>
{ {
try try
{ {
var endpoint = server.ModData.Manifest.Get<WebServices>().ServerAdvertise; var endpoint = server.ModData.Manifest.Get<WebServices>().ServerAdvertise;
using (var wc = new WebClient())
var client = HttpClientFactory.Create();
var response = await client.PostAsync(endpoint, new StringContent(postData));
var masterResponseText = await response.Content.ReadAsStringAsync();
if (isInitialPing)
{ {
wc.Proxy = null; Log.Write("server", "Master server: " + masterResponseText);
var masterResponseText = wc.UploadString(endpoint, postData); var errorCode = 0;
var errorMessage = string.Empty;
if (isInitialPing) if (!string.IsNullOrWhiteSpace(masterResponseText))
{ {
Log.Write("server", "Master server: " + masterResponseText); var regex = new Regex(@"^\[(?<code>-?\d+)\](?<message>.*)");
var errorCode = 0; var match = regex.Match(masterResponseText);
var errorMessage = string.Empty; errorMessage = match.Success && int.TryParse(match.Groups["code"].Value, out errorCode) ?
match.Groups["message"].Value.Trim() : "Failed to parse error message";
}
if (masterResponseText.Length > 0) isInitialPing = false;
lock (masterServerMessages)
{
masterServerMessages.Enqueue("Master server communication established.");
if (errorCode != 0)
{ {
var regex = new Regex(@"^\[(?<code>-?\d+)\](?<message>.*)"); // Hardcoded error messages take precedence over the server-provided messages
var match = regex.Match(masterResponseText); if (!MasterServerErrors.TryGetValue(errorCode, out var message))
errorMessage = match.Success && int.TryParse(match.Groups["code"].Value, out errorCode) ? message = errorMessage;
match.Groups["message"].Value.Trim() : "Failed to parse error message";
}
isInitialPing = false; masterServerMessages.Enqueue("Warning: " + message);
lock (masterServerMessages)
{
masterServerMessages.Enqueue("Master server communication established.");
if (errorCode != 0)
{
// Hardcoded error messages take precedence over the server-provided messages
if (!MasterServerErrors.TryGetValue(errorCode, out var message))
message = errorMessage;
masterServerMessages.Enqueue("Warning: " + message); // Positive error codes indicate errors that prevent advertisement
// Negative error codes are non-fatal warnings
// Positive error codes indicate errors that prevent advertisement if (errorCode > 0)
// Negative error codes are non-fatal warnings masterServerMessages.Enqueue("Game has not been advertised online.");
if (errorCode > 0)
masterServerMessages.Enqueue("Game has not been advertised online.");
}
} }
} }
} }

View File

@@ -10,8 +10,8 @@
#endregion #endregion
using System; using System;
using System.Net; using System.Threading.Tasks;
using System.Text; using OpenRA.Support;
namespace OpenRA.Mods.Common namespace OpenRA.Mods.Common
{ {
@@ -31,34 +31,31 @@ namespace OpenRA.Mods.Common
public void CheckModVersion() public void CheckModVersion()
{ {
Action<DownloadDataCompletedEventArgs> onComplete = i => Task.Run(async () =>
{ {
if (i.Error != null) var queryURL = new HttpQueryBuilder(VersionCheck)
return;
try
{ {
var data = Encoding.UTF8.GetString(i.Result); { "protocol", VersionCheckProtocol },
{ "engine", Game.EngineVersion },
{ "mod", Game.ModData.Manifest.Id },
{ "version", Game.ModData.Manifest.Metadata.Version }
}.ToString();
var status = ModVersionStatus.Latest; var client = HttpClientFactory.Create();
switch (data)
{
case "outdated": status = ModVersionStatus.Outdated; break;
case "unknown": status = ModVersionStatus.Unknown; break;
case "playtest": status = ModVersionStatus.PlaytestAvailable; break;
}
Game.RunAfterTick(() => ModVersionStatus = status); var httpResponseMessage = await client.GetAsync(queryURL);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
var status = ModVersionStatus.Latest;
switch (result)
{
case "outdated": status = ModVersionStatus.Outdated; break;
case "unknown": status = ModVersionStatus.Unknown; break;
case "playtest": status = ModVersionStatus.PlaytestAvailable; break;
} }
catch { }
};
var queryURL = VersionCheck + "?protocol={0}&engine={1}&mod={2}&version={3}".F( Game.RunAfterTick(() => ModVersionStatus = status);
VersionCheckProtocol, });
Uri.EscapeUriString(Game.EngineVersion),
Uri.EscapeUriString(Game.ModData.Manifest.Id),
Uri.EscapeUriString(Game.ModData.Manifest.Metadata.Version));
new Download(queryURL, _ => { }, onComplete);
} }
} }
} }

View File

@@ -11,10 +11,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Net; using System.Net.Http;
using System.Text; using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using OpenRA.Support; using OpenRA.Support;
using OpenRA.Widgets; using OpenRA.Widgets;
@@ -65,47 +65,35 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON"); var cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); void OnDownloadProgress(long total, long read, int progressPercentage)
Action deleteTempFile = () =>
{
Log.Write("install", "Deleting temporary file " + file);
File.Delete(file);
};
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i =>
{ {
var dataReceived = 0.0f; var dataReceived = 0.0f;
var dataTotal = 0.0f; var dataTotal = 0.0f;
var mag = 0; var mag = 0;
var dataSuffix = ""; var dataSuffix = "";
if (i.TotalBytesToReceive < 0) if (total < 0)
{ {
mag = (int)Math.Log(i.BytesReceived, 1024); mag = (int)Math.Log(read, 1024);
dataReceived = i.BytesReceived / (float)(1L << (mag * 10)); dataReceived = read / (float)(1L << (mag * 10));
dataSuffix = SizeSuffixes[mag]; dataSuffix = SizeSuffixes[mag];
getStatusText = () => "Downloading from {2} {0:0.00} {1}".F(dataReceived, getStatusText = () => "Downloading from {2} {0:0.00} {1}".F(dataReceived, dataSuffix, downloadHost ?? "unknown host");
dataSuffix,
downloadHost ?? "unknown host");
progressBar.Indeterminate = true; progressBar.Indeterminate = true;
} }
else else
{ {
mag = (int)Math.Log(i.TotalBytesToReceive, 1024); mag = (int)Math.Log(total, 1024);
dataTotal = i.TotalBytesToReceive / (float)(1L << (mag * 10)); dataTotal = total / (float)(1L << (mag * 10));
dataReceived = i.BytesReceived / (float)(1L << (mag * 10)); dataReceived = read / (float)(1L << (mag * 10));
dataSuffix = SizeSuffixes[mag]; dataSuffix = SizeSuffixes[mag];
getStatusText = () => "Downloading from {4} {1:0.00}/{2:0.00} {3} ({0}%)".F(i.ProgressPercentage, getStatusText = () => "Downloading from {4} {1:0.00}/{2:0.00} {3} ({0}%)".F(progressPercentage, dataReceived, dataTotal, dataSuffix, downloadHost ?? "unknown host");
dataReceived, dataTotal, dataSuffix,
downloadHost ?? "unknown host");
progressBar.Indeterminate = false; progressBar.Indeterminate = false;
} }
progressBar.Percentage = i.ProgressPercentage; progressBar.Percentage = progressPercentage;
}; }
Action<string> onExtractProgress = s => Game.RunAfterTick(() => getStatusText = () => s); Action<string> onExtractProgress = s => Game.RunAfterTick(() => getStatusText = () => s);
@@ -120,137 +108,140 @@ namespace OpenRA.Mods.Common.Widgets.Logic
cancelButton.OnClick = Ui.CloseWindow; cancelButton.OnClick = Ui.CloseWindow;
}); });
Action<AsyncCompletedEventArgs> onDownloadComplete = i =>
{
if (i.Cancelled)
{
deleteTempFile();
Game.RunAfterTick(Ui.CloseWindow);
return;
}
if (i.Error != null)
{
deleteTempFile();
onError(Download.FormatErrorMessage(i.Error));
return;
}
// Validate integrity
if (!string.IsNullOrEmpty(download.SHA1))
{
getStatusText = () => "Verifying archive...";
progressBar.Indeterminate = true;
var archiveValid = false;
try
{
using (var stream = File.OpenRead(file))
{
var archiveSHA1 = CryptoUtil.SHA1Hash(stream);
Log.Write("install", "Downloaded SHA1: " + archiveSHA1);
Log.Write("install", "Expected SHA1: " + download.SHA1);
archiveValid = archiveSHA1 == download.SHA1;
}
}
catch (Exception e)
{
Log.Write("install", "SHA1 calculation failed: " + e.ToString());
}
if (!archiveValid)
{
onError("Archive validation failed");
deleteTempFile();
return;
}
}
// Automatically extract
getStatusText = () => "Extracting...";
progressBar.Indeterminate = true;
var extracted = new List<string>();
try
{
using (var stream = File.OpenRead(file))
using (var z = new ZipFile(stream))
{
foreach (var kv in download.Extract)
{
var entry = z.GetEntry(kv.Value);
if (entry == null || !entry.IsFile)
continue;
onExtractProgress("Extracting " + entry.Name);
Log.Write("install", "Extracting " + entry.Name);
var targetPath = Platform.ResolvePath(kv.Key);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
extracted.Add(targetPath);
using (var zz = z.GetInputStream(entry))
using (var f = File.Create(targetPath))
zz.CopyTo(f);
}
z.Close();
}
Game.RunAfterTick(() => { Ui.CloseWindow(); onSuccess(); });
}
catch (Exception e)
{
Log.Write("install", "Archive extraction failed: " + e.ToString());
foreach (var f in extracted)
{
Log.Write("install", "Deleting " + f);
File.Delete(f);
}
onError("Archive extraction failed");
}
finally
{
deleteTempFile();
}
};
Action<string> downloadUrl = url => Action<string> downloadUrl = url =>
{ {
Log.Write("install", "Downloading " + url); Log.Write("install", "Downloading " + url);
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
downloadHost = new Uri(url).Host; downloadHost = new Uri(url).Host;
var dl = new Download(url, file, onDownloadProgress, onDownloadComplete);
cancelButton.OnClick = dl.CancelAsync; cancelButton.OnClick = () =>
{
tokenSource.Cancel();
Game.RunAfterTick(Ui.CloseWindow);
};
retryButton.OnClick = ShowDownloadDialog; retryButton.OnClick = ShowDownloadDialog;
Task.Run(async () =>
{
var file = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
try
{
var client = HttpClientFactory.Create();
var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token);
using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.ReadWrite, 8192, true))
{
await response.ReadAsStreamWithProgress(fileStream, OnDownloadProgress, token);
}
// Validate integrity
if (!string.IsNullOrEmpty(download.SHA1))
{
getStatusText = () => "Verifying archive...";
progressBar.Indeterminate = true;
var archiveValid = false;
try
{
using (var stream = File.OpenRead(file))
{
var archiveSHA1 = CryptoUtil.SHA1Hash(stream);
Log.Write("install", "Downloaded SHA1: " + archiveSHA1);
Log.Write("install", "Expected SHA1: " + download.SHA1);
archiveValid = archiveSHA1 == download.SHA1;
}
}
catch (Exception e)
{
Log.Write("install", "SHA1 calculation failed: " + e.ToString());
}
if (!archiveValid)
{
onError("Archive validation failed");
return;
}
}
// Automatically extract
getStatusText = () => "Extracting...";
progressBar.Indeterminate = true;
var extracted = new List<string>();
try
{
using (var stream = File.OpenRead(file))
using (var z = new ZipFile(stream))
{
foreach (var kv in download.Extract)
{
var entry = z.GetEntry(kv.Value);
if (entry == null || !entry.IsFile)
continue;
onExtractProgress("Extracting " + entry.Name);
Log.Write("install", "Extracting " + entry.Name);
var targetPath = Platform.ResolvePath(kv.Key);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
extracted.Add(targetPath);
using (var zz = z.GetInputStream(entry))
using (var f = File.Create(targetPath))
zz.CopyTo(f);
}
z.Close();
}
Game.RunAfterTick(() =>
{
Ui.CloseWindow();
onSuccess();
});
}
catch (Exception e)
{
Log.Write("install", "Archive extraction failed: " + e.ToString());
foreach (var f in extracted)
{
Log.Write("install", "Deleting " + f);
File.Delete(f);
}
onError("Archive extraction failed");
}
}
catch (Exception e)
{
onError(e.ToString());
}
finally
{
File.Delete(file);
}
}, token);
}; };
if (download.MirrorList != null) if (download.MirrorList != null)
{ {
Log.Write("install", "Fetching mirrors from " + download.MirrorList); Log.Write("install", "Fetching mirrors from " + download.MirrorList);
Action<DownloadDataCompletedEventArgs> onFetchMirrorsComplete = i => Task.Run(async () =>
{ {
progressBar.Indeterminate = true; var client = HttpClientFactory.Create();
var httpResponseMessage = await client.GetAsync(download.MirrorList);
if (i.Cancelled) var result = await httpResponseMessage.Content.ReadAsStringAsync();
{
Game.RunAfterTick(Ui.CloseWindow);
return;
}
if (i.Error != null)
{
onError(Download.FormatErrorMessage(i.Error));
return;
}
try try
{ {
var data = Encoding.UTF8.GetString(i.Result); var mirrorList = result.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
var mirrorList = data.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
downloadUrl(mirrorList.Random(new MersenneTwister())); downloadUrl(mirrorList.Random(new MersenneTwister()));
} }
catch (Exception e) catch (Exception e)
@@ -259,11 +250,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
Log.Write("install", e.ToString()); Log.Write("install", e.ToString());
onError("Online mirror is not available. Please install from an original disc."); onError("Online mirror is not available. Please install from an original disc.");
} }
}; });
var updateMirrors = new Download(download.MirrorList, onDownloadProgress, onFetchMirrorsComplete);
cancelButton.OnClick = updateMirrors.CancelAsync;
retryButton.OnClick = ShowDownloadDialog;
} }
else else
downloadUrl(download.URL); downloadUrl(download.URL);

View File

@@ -14,7 +14,9 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Support;
using OpenRA.Widgets; using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic namespace OpenRA.Mods.Common.Widgets.Logic
@@ -274,19 +276,47 @@ namespace OpenRA.Mods.Common.Widgets.Logic
{ {
if (!fetchedNews) if (!fetchedNews)
{ {
// Send the mod and engine version to support version-filtered news (update prompts) Task.Run(async () =>
var newsURL = "{0}?version={1}&mod={2}&modversion={3}".F( {
webServices.GameNews, try
Uri.EscapeUriString(Game.EngineVersion), {
Uri.EscapeUriString(Game.ModData.Manifest.Id), var client = HttpClientFactory.Create();
Uri.EscapeUriString(Game.ModData.Manifest.Metadata.Version));
// Parameter string is blank if the player has opted out // Send the mod and engine version to support version-filtered news (update prompts)
newsURL += SystemInfoPromptLogic.CreateParameterString(); var url = new HttpQueryBuilder(webServices.GameNews)
{
{ "version", Game.EngineVersion },
{ "mod", Game.ModData.Manifest.Id },
{ "modversion", Game.ModData.Manifest.Metadata.Version }
}.ToString();
new Download(newsURL, cacheFile, e => { }, // Parameter string is blank if the player has opted out
e => NewsDownloadComplete(e, cacheFile, currentNews, url += SystemInfoPromptLogic.CreateParameterString();
() => OpenNewsPanel(newsButton)));
var response = await client.GetStringAsync(url);
await File.WriteAllTextAsync(cacheFile, response);
Game.RunAfterTick(() => // run on the main thread
{
fetchedNews = true;
var newNews = ParseNews(cacheFile);
if (newNews == null)
return;
DisplayNews(newNews);
if (currentNews == null || newNews.Any(n => !currentNews.Select(c => c.DateTime).Contains(n.DateTime)))
OpenNewsPanel(newsButton);
});
}
catch (Exception e)
{
Game.RunAfterTick(() => // run on the main thread
{
SetNewsStatus("Failed to retrieve news: {0}".F(e));
});
}
});
} }
newsButton.OnClick = () => OpenNewsPanel(newsButton); newsButton.OnClick = () => OpenNewsPanel(newsButton);
@@ -364,28 +394,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return null; return null;
} }
void NewsDownloadComplete(AsyncCompletedEventArgs e, string cacheFile, NewsItem[] oldNews, Action onNewsDownloaded)
{
Game.RunAfterTick(() => // run on the main thread
{
if (e.Error != null)
{
SetNewsStatus("Failed to retrieve news: {0}".F(Download.FormatErrorMessage(e.Error)));
return;
}
fetchedNews = true;
var newNews = ParseNews(cacheFile);
if (newNews == null)
return;
DisplayNews(newNews);
if (oldNews == null || newNews.Any(n => !oldNews.Select(c => c.DateTime).Contains(n.DateTime)))
onNewsDownloaded();
});
}
void DisplayNews(IEnumerable<NewsItem> newsItems) void DisplayNews(IEnumerable<NewsItem> newsItems)
{ {
newsPanel.RemoveChildren(); newsPanel.RemoveChildren();

View File

@@ -11,10 +11,10 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Net; using System.Threading.Tasks;
using System.Text;
using OpenRA.Graphics; using OpenRA.Graphics;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Support;
using OpenRA.Widgets; using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic namespace OpenRA.Mods.Common.Widgets.Logic
@@ -157,79 +157,81 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var messageText = "Loading player profile..."; var messageText = "Loading player profile...";
var messageWidth = messageFont.Measure(messageText).X + 2 * message.Bounds.Left; var messageWidth = messageFont.Measure(messageText).X + 2 * message.Bounds.Left;
Action<DownloadDataCompletedEventArgs> onQueryComplete = i => Task.Run(async () =>
{ {
try try
{ {
if (i.Error == null) var httpClient = HttpClientFactory.Create();
var httpResponseMessage = await httpClient.GetAsync(playerDatabase.Profile + client.Fingerprint);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
var yaml = MiniYaml.FromString(result).First();
if (yaml.Key == "Player")
{ {
var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First(); profile = FieldLoader.Load<PlayerProfile>(yaml.Value);
if (yaml.Key == "Player") Game.RunAfterTick(() =>
{ {
profile = FieldLoader.Load<PlayerProfile>(yaml.Value); var nameLabel = profileHeader.Get<LabelWidget>("PROFILE_NAME");
Game.RunAfterTick(() => var nameFont = Game.Renderer.Fonts[nameLabel.Font];
var rankLabel = profileHeader.Get<LabelWidget>("PROFILE_RANK");
var rankFont = Game.Renderer.Fonts[rankLabel.Font];
var adminContainer = profileHeader.Get("GAME_ADMIN");
var adminLabel = adminContainer.Get<LabelWidget>("LABEL");
var adminFont = Game.Renderer.Fonts[adminLabel.Font];
var headerSizeOffset = profileHeader.Bounds.Height - messageHeader.Bounds.Height;
nameLabel.GetText = () => profile.ProfileName;
rankLabel.GetText = () => profile.ProfileRank;
profileWidth = Math.Max(profileWidth, nameFont.Measure(profile.ProfileName).X + 2 * nameLabel.Bounds.Left);
profileWidth = Math.Max(profileWidth, rankFont.Measure(profile.ProfileRank).X + 2 * rankLabel.Bounds.Left);
header.Bounds.Height += headerSizeOffset;
badgeContainer.Bounds.Y += header.Bounds.Height;
if (client.IsAdmin)
{ {
var nameLabel = profileHeader.Get<LabelWidget>("PROFILE_NAME"); profileWidth = Math.Max(profileWidth, adminFont.Measure(adminLabel.Text).X + 2 * adminLabel.Bounds.Left);
var nameFont = Game.Renderer.Fonts[nameLabel.Font];
var rankLabel = profileHeader.Get<LabelWidget>("PROFILE_RANK");
var rankFont = Game.Renderer.Fonts[rankLabel.Font];
var adminContainer = profileHeader.Get("GAME_ADMIN"); adminContainer.IsVisible = () => true;
var adminLabel = adminContainer.Get<LabelWidget>("LABEL"); profileHeader.Bounds.Height += adminLabel.Bounds.Height;
var adminFont = Game.Renderer.Fonts[adminLabel.Font]; header.Bounds.Height += adminLabel.Bounds.Height;
badgeContainer.Bounds.Y += adminLabel.Bounds.Height;
}
var headerSizeOffset = profileHeader.Bounds.Height - messageHeader.Bounds.Height; Func<int, int> negotiateWidth = badgeWidth =>
{
profileWidth = Math.Min(Math.Max(badgeWidth, profileWidth), maxProfileWidth);
return profileWidth;
};
nameLabel.GetText = () => profile.ProfileName; if (profile.Badges.Any())
rankLabel.GetText = () => profile.ProfileRank; {
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
profileWidth = Math.Max(profileWidth, nameFont.Measure(profile.ProfileName).X + 2 * nameLabel.Bounds.Left);
profileWidth = Math.Max(profileWidth, rankFont.Measure(profile.ProfileRank).X + 2 * rankLabel.Bounds.Left);
header.Bounds.Height += headerSizeOffset;
badgeContainer.Bounds.Y += header.Bounds.Height;
if (client.IsAdmin)
{ {
profileWidth = Math.Max(profileWidth, adminFont.Measure(adminLabel.Text).X + 2 * adminLabel.Bounds.Left); { "worldRenderer", worldRenderer },
{ "profile", profile },
{ "negotiateWidth", negotiateWidth }
});
adminContainer.IsVisible = () => true; if (badges.Bounds.Height > 0)
profileHeader.Bounds.Height += adminLabel.Bounds.Height; {
header.Bounds.Height += adminLabel.Bounds.Height; badgeContainer.Bounds.Height = badges.Bounds.Height;
badgeContainer.Bounds.Y += adminLabel.Bounds.Height; badgeContainer.IsVisible = () => true;
} }
}
Func<int, int> negotiateWidth = badgeWidth => profileWidth = Math.Min(profileWidth, maxProfileWidth);
{ header.Bounds.Width = widget.Bounds.Width = badgeContainer.Bounds.Width = profileWidth;
profileWidth = Math.Min(Math.Max(badgeWidth, profileWidth), maxProfileWidth); widget.Bounds.Height = header.Bounds.Height + badgeContainer.Bounds.Height;
return profileWidth;
};
if (profile.Badges.Any()) if (badgeSeparator != null)
{ badgeSeparator.Bounds.Width = profileWidth - 2 * badgeSeparator.Bounds.X;
var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs()
{
{ "worldRenderer", worldRenderer },
{ "profile", profile },
{ "negotiateWidth", negotiateWidth }
});
if (badges.Bounds.Height > 0) profileLoaded = true;
{ });
badgeContainer.Bounds.Height = badges.Bounds.Height;
badgeContainer.IsVisible = () => true;
}
}
profileWidth = Math.Min(profileWidth, maxProfileWidth);
header.Bounds.Width = widget.Bounds.Width = badgeContainer.Bounds.Width = profileWidth;
widget.Bounds.Height = header.Bounds.Height + badgeContainer.Bounds.Height;
if (badgeSeparator != null)
badgeSeparator.Bounds.Width = profileWidth - 2 * badgeSeparator.Bounds.X;
profileLoaded = true;
});
}
} }
} }
catch (Exception e) catch (Exception e)
@@ -245,15 +247,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic
header.Bounds.Width = widget.Bounds.Width = messageWidth; header.Bounds.Width = widget.Bounds.Width = messageWidth;
} }
} }
}; });
message.GetText = () => messageText; message.GetText = () => messageText;
header.Bounds.Height += messageHeader.Bounds.Height; header.Bounds.Height += messageHeader.Bounds.Height;
header.Bounds.Width = widget.Bounds.Width = messageWidth; header.Bounds.Width = widget.Bounds.Width = messageWidth;
widget.Bounds.Height = header.Bounds.Height; widget.Bounds.Height = header.Bounds.Height;
badgeContainer.Visible = false; badgeContainer.Visible = false;
new Download(playerDatabase.Profile + client.Fingerprint, _ => { }, onQueryComplete);
} }
} }

View File

@@ -12,12 +12,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Threading.Tasks;
using System.Text;
using BeaconLib; using BeaconLib;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Server; using OpenRA.Server;
using OpenRA.Support;
using OpenRA.Traits; using OpenRA.Traits;
using OpenRA.Widgets; using OpenRA.Widgets;
@@ -59,7 +59,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
SearchStatus searchStatus = SearchStatus.Fetching; SearchStatus searchStatus = SearchStatus.Fetching;
Download currentQuery; bool activeQuery;
IEnumerable<BeaconLocation> lanGameLocations; IEnumerable<BeaconLocation> lanGameLocations;
public string ProgressLabelText() public string ProgressLabelText()
@@ -322,41 +322,48 @@ namespace OpenRA.Mods.Common.Widgets.Logic
public void RefreshServerList() public void RefreshServerList()
{ {
// Query in progress // Query in progress
if (currentQuery != null) if (activeQuery)
return; return;
searchStatus = SearchStatus.Fetching; searchStatus = SearchStatus.Fetching;
Action<DownloadDataCompletedEventArgs> onComplete = i => var queryURL = new HttpQueryBuilder(services.ServerList)
{ {
currentQuery = null; { "protocol", GameServer.ProtocolVersion },
{ "engine", Game.EngineVersion },
{ "mod", Game.ModData.Manifest.Id },
{ "version", Game.ModData.Manifest.Metadata.Version }
}.ToString();
List<GameServer> games = null; Task.Run(async () =>
if (i.Error == null) {
var games = new List<GameServer>();
var client = HttpClientFactory.Create();
var httpResponseMessage = await client.GetAsync(queryURL);
var result = await httpResponseMessage.Content.ReadAsStringAsync();
activeQuery = true;
try
{ {
games = new List<GameServer>(); var yaml = MiniYaml.FromString(result);
try foreach (var node in yaml)
{ {
var data = Encoding.UTF8.GetString(i.Result); try
var yaml = MiniYaml.FromString(data);
foreach (var node in yaml)
{ {
try var gs = new GameServer(node.Value);
{ if (gs.Address != null)
var gs = new GameServer(node.Value); games.Add(gs);
if (gs.Address != null) }
games.Add(gs); catch
} {
catch // Ignore any invalid games advertised.
{
// Ignore any invalid games advertised.
}
} }
} }
catch }
{ catch
searchStatus = SearchStatus.Failed; {
} searchStatus = SearchStatus.Failed;
} }
var lanGames = new List<GameServer>(); var lanGames = new List<GameServer>();
@@ -398,15 +405,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
games = groupedLanGames.ToList(); games = groupedLanGames.ToList();
Game.RunAfterTick(() => RefreshServerListInner(games)); Game.RunAfterTick(() => RefreshServerListInner(games));
};
var queryURL = services.ServerList + "?protocol={0}&engine={1}&mod={2}&version={3}".F( activeQuery = false;
GameServer.ProtocolVersion, });
Uri.EscapeUriString(Game.EngineVersion),
Uri.EscapeUriString(Game.ModData.Manifest.Id),
Uri.EscapeUriString(Game.ModData.Manifest.Metadata.Version));
currentQuery = new Download(queryURL, _ => { }, onComplete);
} }
int GroupSortOrder(GameServer testEntry) int GroupSortOrder(GameServer testEntry)