now it works
This commit is contained in:
23
Mlaumcherb.Client.Avalonia/сеть/DataSize.cs
Normal file
23
Mlaumcherb.Client.Avalonia/сеть/DataSize.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть;
|
||||
|
||||
public record struct DataSize(long Bytes)
|
||||
{
|
||||
static string BytesToHumanReadable(long bytes)
|
||||
{
|
||||
long K = bytes / 1024;
|
||||
if (K == 0)
|
||||
return $"{bytes}b";
|
||||
float M = K / 1024f;
|
||||
if (M < 1)
|
||||
return $"{K:N1}Kb";
|
||||
float G = M / 1024f;
|
||||
if (G < 1)
|
||||
return $"{M:N1}Mb";
|
||||
return $"{G:N1}Gb";
|
||||
}
|
||||
|
||||
public override string ToString() => BytesToHumanReadable(Bytes);
|
||||
|
||||
public static implicit operator DataSize(long bytes) => new DataSize(bytes);
|
||||
}
|
||||
|
||||
61
Mlaumcherb.Client.Avalonia/сеть/NetworkProgressReporter.cs
Normal file
61
Mlaumcherb.Client.Avalonia/сеть/NetworkProgressReporter.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using Mlaumcherb.Client.Avalonia.зримое;
|
||||
using Timer = DTLib.Timer;
|
||||
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть;
|
||||
|
||||
public record struct DownloadProgress(DataSize Downloaded, DataSize Total, DataSize PerSecond)
|
||||
{
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Downloaded}/{Total} ({PerSecond}/s)";
|
||||
}
|
||||
}
|
||||
|
||||
public class NetworkProgressReporter : IDisposable
|
||||
{
|
||||
private readonly Action<DownloadProgress> _reportProgressDelegate;
|
||||
private long _totalSize;
|
||||
private long _curSize;
|
||||
private long _prevSize;
|
||||
private int _timerDelay = 1000;
|
||||
private Timer _timer;
|
||||
|
||||
public NetworkProgressReporter(long totalSize, Action<DownloadProgress> reportProgressDelegate)
|
||||
{
|
||||
_totalSize = totalSize;
|
||||
_reportProgressDelegate = reportProgressDelegate;
|
||||
_timer = new Timer(true, _timerDelay, ReportProgress);
|
||||
}
|
||||
|
||||
// atomic add
|
||||
public void AddBytesCount(byte[] buffer, int offset, int count)
|
||||
{
|
||||
Interlocked.Add(ref _curSize, count);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.InvokeAction();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void ReportProgress()
|
||||
{
|
||||
long bytesPerSec = (_curSize - _prevSize) / (_timerDelay / 1000);
|
||||
_prevSize = _curSize;
|
||||
var p = new DownloadProgress(_curSize, _totalSize, bytesPerSec);
|
||||
LauncherApp.Logger.LogDebug(nameof(ReportProgress),
|
||||
$"download progress {p}");
|
||||
_reportProgressDelegate(p);
|
||||
}
|
||||
}
|
||||
77
Mlaumcherb.Client.Avalonia/сеть/NetworkTask.cs
Normal file
77
Mlaumcherb.Client.Avalonia/сеть/NetworkTask.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть;
|
||||
|
||||
public class NetworkTask : IDisposable
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public enum Status
|
||||
{
|
||||
Initialized,
|
||||
Started,
|
||||
Completed,
|
||||
Cancelled,
|
||||
Failed
|
||||
};
|
||||
|
||||
public Status DownloadStatus { get; private set; } = Status.Initialized;
|
||||
|
||||
public event Action? OnStart;
|
||||
public event Action<DownloadProgress>? OnProgress;
|
||||
public event Action<Status>? OnStop;
|
||||
|
||||
public delegate Task DownloadAction(NetworkProgressReporter progressReporter, CancellationToken ct);
|
||||
|
||||
private readonly DownloadAction _downloadAction;
|
||||
private CancellationTokenSource _cts = new();
|
||||
private NetworkProgressReporter _progressReporter;
|
||||
|
||||
public NetworkTask(string name, long dataSize, DownloadAction downloadAction)
|
||||
{
|
||||
Name = name;
|
||||
_downloadAction = downloadAction;
|
||||
_progressReporter = new NetworkProgressReporter(dataSize, ReportProgress);
|
||||
}
|
||||
|
||||
private void ReportProgress(DownloadProgress p) => OnProgress?.Invoke(p);
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if(DownloadStatus == Status.Started || DownloadStatus == Status.Completed)
|
||||
return;
|
||||
DownloadStatus = Status.Started;
|
||||
try
|
||||
{
|
||||
_progressReporter.Start();
|
||||
OnStart?.Invoke();
|
||||
await _downloadAction(_progressReporter, _cts.Token);
|
||||
DownloadStatus = Status.Completed;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
DownloadStatus = Status.Cancelled;
|
||||
}
|
||||
catch
|
||||
{
|
||||
DownloadStatus = Status.Failed;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_progressReporter.Stop();
|
||||
OnStop?.Invoke(DownloadStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
DownloadStatus = Status.Cancelled;
|
||||
_cts.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts.Dispose();
|
||||
_progressReporter.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using DTLib.Extensions;
|
||||
using Mlaumcherb.Client.Avalonia.зримое;
|
||||
using Mlaumcherb.Client.Avalonia.классы;
|
||||
using Mlaumcherb.Client.Avalonia.холопы;
|
||||
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
|
||||
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
|
||||
|
||||
public class AssetsDownloadTaskFactory : INetworkTaskFactory
|
||||
{
|
||||
private const string ASSET_SERVER_URL = "https://resources.download.minecraft.net/";
|
||||
private GameVersionDescriptor _descriptor;
|
||||
private SHA1 _hasher;
|
||||
private IOPath _indexFilePath;
|
||||
List<AssetDownloadProperties> _assetsToDownload = new();
|
||||
|
||||
public AssetsDownloadTaskFactory(GameVersionDescriptor descriptor)
|
||||
{
|
||||
_descriptor = descriptor;
|
||||
_hasher = SHA1.Create();
|
||||
_indexFilePath = PathHelper.GetAssetIndexFilePath(_descriptor.assetIndex.id);
|
||||
}
|
||||
|
||||
public async Task<NetworkTask?> CreateAsync(bool checkHashes)
|
||||
{
|
||||
if (!await CheckFilesAsync(checkHashes))
|
||||
return new NetworkTask(
|
||||
$"assets '{_descriptor.assetIndex.id}'",
|
||||
GetTotalSize(),
|
||||
Download
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
private async Task<bool> CheckFilesAsync(bool checkHashes)
|
||||
{
|
||||
if(!File.Exists(_indexFilePath))
|
||||
{
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading asset index to '{_indexFilePath}'");
|
||||
await DownloadFile(_descriptor.assetIndex.url, _indexFilePath);
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), "finished downloading asset index");
|
||||
}
|
||||
|
||||
string indexFileText = File.ReadAllText(_indexFilePath);
|
||||
var assetIndex = JsonConvert.DeserializeObject<AssetIndex>(indexFileText)
|
||||
?? throw new Exception($"can't deserialize asset index file '{_indexFilePath}'");
|
||||
|
||||
_assetsToDownload.Clear();
|
||||
// removing duplicates for Dictionary (idk how can it be possible, but Newtonsoft.Json creates them)
|
||||
HashSet<string> assetHashes = new HashSet<string>();
|
||||
foreach (var pair in assetIndex.objects)
|
||||
{
|
||||
if (assetHashes.Add(pair.Value.hash))
|
||||
{
|
||||
var a = new AssetDownloadProperties(pair.Key, pair.Value);
|
||||
if (!File.Exists(a.filePath))
|
||||
{
|
||||
_assetsToDownload.Add(a);
|
||||
}
|
||||
else if(checkHashes)
|
||||
{
|
||||
await using var fs = File.OpenRead(a.filePath);
|
||||
string hash = _hasher.ComputeHash(fs).HashToString();
|
||||
if (hash != a.hash)
|
||||
_assetsToDownload.Add(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _assetsToDownload.Count == 0;
|
||||
}
|
||||
|
||||
private long GetTotalSize()
|
||||
{
|
||||
long totalSize = 0;
|
||||
foreach (var a in _assetsToDownload)
|
||||
totalSize += a.size;
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
private class AssetDownloadProperties
|
||||
{
|
||||
public string name;
|
||||
public string hash;
|
||||
public long size;
|
||||
public string url;
|
||||
public IOPath filePath;
|
||||
|
||||
public AssetDownloadProperties(string key, AssetProperties p)
|
||||
{
|
||||
name = key;
|
||||
hash = p.hash;
|
||||
size = p.size;
|
||||
string hashStart = hash.Substring(0, 2);
|
||||
url = $"{ASSET_SERVER_URL}/{hashStart}/{hash}";
|
||||
filePath = Path.Concat(IOPath.ArrayCast(["assets", "objects", hashStart, hash], true));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
|
||||
{
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading assets '{_descriptor.assetIndex.id}'");
|
||||
ParallelOptions opt = new()
|
||||
{
|
||||
MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads,
|
||||
CancellationToken = ct
|
||||
};
|
||||
await Parallel.ForEachAsync(_assetsToDownload, opt,
|
||||
async (a, _ct) =>
|
||||
{
|
||||
bool completed = false;
|
||||
while(!completed)
|
||||
{
|
||||
LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading asset '{a.name}' {a.hash}");
|
||||
try
|
||||
{
|
||||
await DownloadFile(a.url, a.filePath, _ct, pr.AddBytesCount);
|
||||
completed = true;
|
||||
}
|
||||
catch (HttpRequestException httpException)
|
||||
{
|
||||
// wait on rate limit
|
||||
if(httpException.StatusCode == HttpStatusCode.TooManyRequests)
|
||||
{
|
||||
LauncherApp.Logger.LogDebug(nameof(Сеть), "rate limit hit");
|
||||
await Task.Delay(1000, _ct);
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
});
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), $"finished downloading assets '{_descriptor.assetIndex.id}'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
|
||||
|
||||
public interface INetworkTaskFactory
|
||||
{
|
||||
/// <returns>unstarted network task or null if there is nothing to download</returns>
|
||||
Task<NetworkTask?> CreateAsync(bool checkHashes);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Security.Cryptography;
|
||||
using DTLib.Extensions;
|
||||
using Mlaumcherb.Client.Avalonia.зримое;
|
||||
using Mlaumcherb.Client.Avalonia.классы;
|
||||
using Mlaumcherb.Client.Avalonia.холопы;
|
||||
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
|
||||
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
|
||||
|
||||
public class JavaDownloadTaskFactory : INetworkTaskFactory
|
||||
{
|
||||
private const string CATALOG_URL =
|
||||
"https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json";
|
||||
private GameVersionDescriptor _descriptor;
|
||||
private IOPath _javaVersionDir;
|
||||
private SHA1 _hasher;
|
||||
private JavaDistributiveManifest? _distributiveManifest;
|
||||
private List<(IOPath path, JavaDistributiveElementProps props)> _filesToDownload = new();
|
||||
|
||||
public JavaDownloadTaskFactory(GameVersionDescriptor descriptor)
|
||||
{
|
||||
_descriptor = descriptor;
|
||||
_javaVersionDir = PathHelper.GetJavaRuntimeDir(_descriptor.javaVersion.component);
|
||||
_hasher = SHA1.Create();
|
||||
}
|
||||
|
||||
public async Task<NetworkTask?> CreateAsync(bool checkHashes)
|
||||
{
|
||||
var catalog = await DownloadStringAndDeserialize<JavaVersionCatalog>(CATALOG_URL);
|
||||
var versionProps = catalog.GetVersionProps(_descriptor.javaVersion);
|
||||
_distributiveManifest = await DownloadStringAndDeserialize<JavaDistributiveManifest>(versionProps.manifest.url);
|
||||
|
||||
NetworkTask? networkTask = null;
|
||||
if (!CheckFiles(checkHashes))
|
||||
networkTask = new(
|
||||
$"java runtime '{_descriptor.javaVersion.component}'",
|
||||
GetTotalSize(),
|
||||
Download
|
||||
);
|
||||
return networkTask;
|
||||
}
|
||||
|
||||
private bool CheckFiles(bool checkHashes)
|
||||
{
|
||||
_filesToDownload.Clear();
|
||||
foreach (var pair in _distributiveManifest!.files)
|
||||
{
|
||||
if (pair.Value.type != "file")
|
||||
continue;
|
||||
|
||||
if (pair.Value.downloads != null)
|
||||
{
|
||||
var artifact = pair.Value.downloads;
|
||||
IOPath file_path = Path.Concat(_javaVersionDir, pair.Key);
|
||||
if (!File.Exists(file_path))
|
||||
{
|
||||
_filesToDownload.Add((file_path, pair.Value));
|
||||
}
|
||||
else if(checkHashes)
|
||||
{
|
||||
using var fs = File.OpenRead(file_path);
|
||||
if (_hasher.ComputeHash(fs).HashToString() != artifact.raw.sha1)
|
||||
{
|
||||
_filesToDownload.Add((file_path, pair.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _filesToDownload.Count == 0;
|
||||
}
|
||||
|
||||
private long GetTotalSize()
|
||||
{
|
||||
long totalSize = 0;
|
||||
foreach (var file in _filesToDownload)
|
||||
{
|
||||
if(file.props.downloads == null)
|
||||
continue;
|
||||
totalSize += file.props.downloads.lzma?.size ?? file.props.downloads.raw.size;
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
|
||||
{
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), "started downloading java runtime " +
|
||||
$"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'");
|
||||
|
||||
ParallelOptions opt = new()
|
||||
{
|
||||
MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads,
|
||||
CancellationToken = ct
|
||||
};
|
||||
await Parallel.ForEachAsync(_filesToDownload, opt, async (f, _ct) =>
|
||||
{
|
||||
if (f.props.downloads!.lzma != null)
|
||||
{
|
||||
LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading lzma-compressed file '{f.path}'");
|
||||
await using var pipe = new TransformStream(await GetStream(f.props.downloads.lzma.url, _ct));
|
||||
pipe.AddTransform(pr.AddBytesCount);
|
||||
await using var fs = File.OpenWrite(f.path);
|
||||
LZMAHelper.Decompress(pipe, fs);
|
||||
}
|
||||
else
|
||||
{
|
||||
LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading raw file '{f.path}'");
|
||||
await DownloadFile(f.props.downloads.raw.url, f.path, _ct, pr.AddBytesCount);
|
||||
}
|
||||
|
||||
if(!OperatingSystem.IsWindows() && f.props.executable is true)
|
||||
{
|
||||
LauncherApp.Logger.LogDebug(nameof(Сеть), $"adding execute rights to file '{f.path}'");
|
||||
System.IO.File.SetUnixFileMode(f.path.ToString(), UnixFileMode.UserExecute);
|
||||
}
|
||||
});
|
||||
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), "finished downloading java runtime " +
|
||||
$"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using DTLib.Extensions;
|
||||
using Mlaumcherb.Client.Avalonia.зримое;
|
||||
using Mlaumcherb.Client.Avalonia.классы;
|
||||
using Mlaumcherb.Client.Avalonia.холопы;
|
||||
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
|
||||
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
|
||||
|
||||
public class LibrariesDownloadTaskFactory : INetworkTaskFactory
|
||||
{
|
||||
private GameVersionDescriptor _descriptor;
|
||||
private Libraries _libraries;
|
||||
private SHA1 _hasher;
|
||||
private List<Libraries.JarLib> _libsToDownload = new();
|
||||
private IOPath _nativesDir;
|
||||
|
||||
public LibrariesDownloadTaskFactory(GameVersionDescriptor descriptor, Libraries libraries)
|
||||
{
|
||||
_descriptor = descriptor;
|
||||
_libraries = libraries;
|
||||
_hasher = SHA1.Create();
|
||||
_nativesDir = PathHelper.GetNativeLibrariesDir(descriptor.id);
|
||||
}
|
||||
|
||||
public Task<NetworkTask?> CreateAsync(bool checkHashes)
|
||||
{
|
||||
NetworkTask? networkTask = null;
|
||||
if (!CheckFiles(checkHashes))
|
||||
networkTask = new NetworkTask(
|
||||
$"libraries '{_descriptor.id}'",
|
||||
GetTotalSize(),
|
||||
Download
|
||||
);
|
||||
return Task.FromResult(networkTask);
|
||||
}
|
||||
|
||||
private bool CheckFiles(bool checkHashes)
|
||||
{
|
||||
_libsToDownload.Clear();
|
||||
bool nativeDirExists = Directory.Exists(_nativesDir);
|
||||
|
||||
foreach (var l in _libraries.Libs)
|
||||
{
|
||||
if (!File.Exists(l.jarFilePath))
|
||||
{
|
||||
_libsToDownload.Add(l);
|
||||
}
|
||||
else if (!nativeDirExists && l is Libraries.NativeLib)
|
||||
{
|
||||
_libsToDownload.Add(l);
|
||||
}
|
||||
else if (checkHashes)
|
||||
{
|
||||
using var fs = File.OpenRead(l.jarFilePath);
|
||||
string hash = _hasher.ComputeHash(fs).HashToString();
|
||||
if(hash != l.artifact.sha1)
|
||||
_libsToDownload.Add(l);
|
||||
}
|
||||
}
|
||||
|
||||
return _libsToDownload.Count == 0;
|
||||
}
|
||||
|
||||
private long GetTotalSize()
|
||||
{
|
||||
long total = 0;
|
||||
foreach (var l in _libsToDownload)
|
||||
total += l.artifact.size;
|
||||
return total;
|
||||
}
|
||||
|
||||
private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
|
||||
{
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading libraries '{_descriptor.id}'");
|
||||
|
||||
ParallelOptions opt = new()
|
||||
{
|
||||
MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads,
|
||||
CancellationToken = ct
|
||||
};
|
||||
await Parallel.ForEachAsync(_libsToDownload, opt, async (l, _ct) =>
|
||||
{
|
||||
LauncherApp.Logger.LogDebug(nameof(Сеть), $"downloading library '{l.name}' to '{l.jarFilePath}'");
|
||||
await DownloadFile(l.artifact.url, l.jarFilePath, _ct, pr.AddBytesCount);
|
||||
if (l is Libraries.NativeLib n)
|
||||
{
|
||||
var zipf = File.OpenRead(n.jarFilePath);
|
||||
ZipFile.ExtractToDirectory(zipf, _nativesDir.ToString(), true);
|
||||
if (n.extractionOptions?.exclude != null)
|
||||
{
|
||||
foreach (var excluded in n.extractionOptions.exclude)
|
||||
{
|
||||
IOPath path = Path.Concat(_nativesDir, excluded);
|
||||
if(Directory.Exists(path))
|
||||
Directory.Delete(path);
|
||||
if(File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), $"finished downloading libraries '{_descriptor.id}'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Security.Cryptography;
|
||||
using DTLib.Extensions;
|
||||
using Mlaumcherb.Client.Avalonia.зримое;
|
||||
using Mlaumcherb.Client.Avalonia.классы;
|
||||
using Mlaumcherb.Client.Avalonia.холопы;
|
||||
using static Mlaumcherb.Client.Avalonia.сеть.Сеть;
|
||||
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories;
|
||||
|
||||
public class VersionFileDownloadTaskFactory : INetworkTaskFactory
|
||||
{
|
||||
private GameVersionDescriptor _descriptor;
|
||||
private IOPath _filePath;
|
||||
private SHA1 _hasher;
|
||||
|
||||
public VersionFileDownloadTaskFactory(GameVersionDescriptor descriptor)
|
||||
{
|
||||
_descriptor = descriptor;
|
||||
_filePath = PathHelper.GetVersionJarFilePath(_descriptor.id);
|
||||
_hasher = SHA1.Create();
|
||||
}
|
||||
|
||||
public Task<NetworkTask?> CreateAsync(bool checkHashes)
|
||||
{
|
||||
NetworkTask? networkTask = null;
|
||||
if (!CheckFiles(checkHashes))
|
||||
networkTask = new NetworkTask(
|
||||
$"version file '{_descriptor.id}'",
|
||||
GetTotalSize(),
|
||||
Download
|
||||
);
|
||||
return Task.FromResult(networkTask);
|
||||
}
|
||||
|
||||
private bool CheckFiles(bool checkHashes)
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
return false;
|
||||
if (!checkHashes)
|
||||
return true;
|
||||
using var fs = File.OpenRead(_filePath);
|
||||
string hash = _hasher.ComputeHash(fs).HashToString();
|
||||
return hash == _descriptor.downloads.client.sha1;
|
||||
}
|
||||
|
||||
private long GetTotalSize()
|
||||
{
|
||||
return _descriptor.downloads.client.size;
|
||||
}
|
||||
|
||||
private async Task Download(NetworkProgressReporter pr, CancellationToken ct)
|
||||
{
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), $"started downloading version file '{_descriptor.id}'");
|
||||
await DownloadFile(_descriptor.downloads.client.url, _filePath, ct, pr.AddBytesCount);
|
||||
LauncherApp.Logger.LogInfo(nameof(Сеть), $"finished downloading version file '{_descriptor.id}'");
|
||||
}
|
||||
}
|
||||
87
Mlaumcherb.Client.Avalonia/сеть/Сеть.cs
Normal file
87
Mlaumcherb.Client.Avalonia/сеть/Сеть.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Net.Http;
|
||||
using Mlaumcherb.Client.Avalonia.зримое;
|
||||
using Mlaumcherb.Client.Avalonia.классы;
|
||||
|
||||
namespace Mlaumcherb.Client.Avalonia.сеть;
|
||||
|
||||
public static class Сеть
|
||||
{
|
||||
private static HttpClient _http = new();
|
||||
|
||||
static Сеть()
|
||||
{
|
||||
// thanks for Sashok :3
|
||||
// https://github.com/new-sashok724/Launcher/blob/23485c3f7de6620d2c6b7b2dd9339c3beb6a0366/Launcher/source/helper/IOHelper.java#L259
|
||||
_http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");
|
||||
}
|
||||
|
||||
public static Task<string> GetString(string url, CancellationToken ct = default) => _http.GetStringAsync(url, ct);
|
||||
public static Task<Stream> GetStream(string url, CancellationToken ct = default) => _http.GetStreamAsync(url, ct);
|
||||
|
||||
public static async Task DownloadFile(string url, Stream outStream, CancellationToken ct = default,
|
||||
params TransformStream.TransformFuncDelegate[] transforms)
|
||||
{
|
||||
await using var pipe = new TransformStream(await GetStream(url, ct));
|
||||
if (transforms.Length > 0)
|
||||
pipe.AddTransforms(transforms);
|
||||
await pipe.CopyToAsync(outStream, ct);
|
||||
}
|
||||
|
||||
public static async Task DownloadFile(string url, IOPath outPath, CancellationToken ct = default,
|
||||
params TransformStream.TransformFuncDelegate[] transforms)
|
||||
{
|
||||
await using var file = File.OpenWrite(outPath);
|
||||
await DownloadFile(url, file, ct, transforms);
|
||||
}
|
||||
|
||||
public static async Task<T> DownloadStringAndDeserialize<T>(string url)
|
||||
{
|
||||
var text = await _http.GetStringAsync(url);
|
||||
var result = JsonConvert.DeserializeObject<T>(text)
|
||||
?? throw new Exception($"can't deserialize {typeof(T).Name}");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static readonly string[] VERSION_MANIFEST_URLS =
|
||||
{
|
||||
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
|
||||
};
|
||||
|
||||
private static async Task<List<RemoteVersionDescriptorProps>> GetRemoteVersionDescriptorsAsync()
|
||||
{
|
||||
List<RemoteVersionDescriptorProps> descriptors = new();
|
||||
foreach (var url in VERSION_MANIFEST_URLS)
|
||||
{
|
||||
try
|
||||
{
|
||||
var catalog = await DownloadStringAndDeserialize<GameVersionCatalog>(url);
|
||||
descriptors.AddRange(catalog.versions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LauncherApp.Logger.LogWarn(nameof(Сеть), ex);
|
||||
}
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private static List<GameVersionProps>? _versionPropsList;
|
||||
|
||||
/// <returns>empty list if couldn't find any remote versions</returns>
|
||||
public static async Task<IReadOnlyList<GameVersionProps>> GetDownloadableVersions()
|
||||
{
|
||||
if (_versionPropsList == null)
|
||||
{
|
||||
_versionPropsList = new();
|
||||
var rvdlist = await GetRemoteVersionDescriptorsAsync();
|
||||
foreach (var r in rvdlist)
|
||||
{
|
||||
if (r.type == "release")
|
||||
_versionPropsList.Add(new GameVersionProps(r.id, r.url));
|
||||
}
|
||||
}
|
||||
|
||||
return _versionPropsList;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user