Replace WebClient with HttpClient
This commit is contained in:
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
63
OpenRA.Game/HttpExtension.cs
Normal file
63
OpenRA.Game/HttpExtension.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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)' == ''">
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
48
OpenRA.Game/Support/HttpClientFactory.cs
Normal file
48
OpenRA.Game/Support/HttpClientFactory.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
61
OpenRA.Game/Support/HttpQueryBuilder.cs
Normal file
61
OpenRA.Game/Support/HttpQueryBuilder.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'">
|
||||||
|
|||||||
@@ -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.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user