now it works

This commit is contained in:
2024-11-06 00:04:12 +05:00
parent 612976dfe6
commit 1f663902e2
54 changed files with 763 additions and 586 deletions

View File

@@ -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}'");
}
}

View File

@@ -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);
}

View File

@@ -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}'");
}
}

View File

@@ -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}'");
}
}

View File

@@ -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}'");
}
}