using System.Net; using System.Net.Http; 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 IOPath _indexFilePath; List _assetsToDownload = new(); public AssetsDownloadTaskFactory(GameVersionDescriptor descriptor) { _descriptor = descriptor; _indexFilePath = PathHelper.GetAssetIndexFilePath(_descriptor.assetIndex.id); } public async Task CreateAsync(bool checkHashes) { NetworkTask? networkTask = null; if (!await CheckFilesAsync(checkHashes)) { networkTask = new NetworkTask( $"assets '{_descriptor.assetIndex.id}'", GetTotalSize(), Download ); } return networkTask; } private async Task CheckFilesAsync(bool checkHashes) { (AssetIndex assetIndex, _) = await ReadOrDownloadAndDeserialize( _indexFilePath, _descriptor.assetIndex.url, _descriptor.assetIndex.sha1, checkHashes); // removing duplicates for Dictionary (idk how can it be possible, but Newtonsoft.Json creates them) HashSet assetHashes = new HashSet(); foreach (var pair in assetIndex.objects) { if (assetHashes.Add(pair.Value.hash)) { var a = new AssetDownloadProperties(pair.Key, pair.Value); if (!HashHelper.CheckFileSHA1(a.filePath, a.hash, checkHashes)) { _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}'"); } }