137 lines
5.0 KiB
C#
137 lines
5.0 KiB
C#
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}'");
|
||
}
|
||
} |