mlaumcherb/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/AssetsDownloadTaskFactory.cs
2024-12-27 21:11:19 +05:00

137 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.сеть.NetworkHelper;
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(NetworkHelper), $"started downloading asset index to '{_indexFilePath}'");
await DownloadFile(_descriptor.assetIndex.url, _indexFilePath);
LauncherApp.Logger.LogInfo(nameof(NetworkHelper), "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(NetworkHelper), $"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(NetworkHelper), $"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(NetworkHelper), "rate limit hit");
await Task.Delay(1000, _ct);
}
else throw;
}
}
});
LauncherApp.Logger.LogInfo(nameof(NetworkHelper), $"finished downloading assets '{_descriptor.assetIndex.id}'");
}
}