mlaumcherb/Млаумчерб.Клиент/Сеть.cs
2024-09-27 02:40:53 +05:00

192 lines
7.6 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.Buffers;
using System.Net.Http;
using Млаумчерб.Клиент.видимое;
using Млаумчерб.Клиент.классы;
using Timer = DTLib.Timer;
namespace Млаумчерб.Клиент;
public static class Сеть
{
private static HttpClient http = new();
private const string ASSET_SERVER_URL = "https://resources.download.minecraft.net/";
private static readonly string[] VERSION_MANIFEST_URLS = {
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
};
public static async Task DownloadFileHTTP(string url, IOPath outPath,
Action<ArraySegment<byte>>? transformFunc = null, CancellationToken ct = default)
{
await using var src = await http.GetStreamAsync(url, ct);
await using var dst = File.OpenWrite(outPath);
await src.CopyTransformAsync(dst, transformFunc, ct).ConfigureAwait(false);
}
public static async Task CopyTransformAsync(this Stream src, Stream dst,
Action<ArraySegment<byte>>? transformFunc = null, CancellationToken ct = default)
{
// default dotnet runtime buffer size
int bufferSize = 81920;
byte[] readBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
byte[] writeBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
var readTask = src.ReadAsync(readBuffer, 0, bufferSize, ct).ConfigureAwait(false);
while (true)
{
int readCount = await readTask;
if (readCount == 0)
break;
(readBuffer, writeBuffer) = (writeBuffer, readBuffer);
readTask = src.ReadAsync(readBuffer, 0, bufferSize, ct).ConfigureAwait(false);
transformFunc?.Invoke(new ArraySegment<byte>(writeBuffer, 0, readCount));
dst.Write(writeBuffer, 0, readCount);
}
}
catch (OperationCanceledException)
{ }
finally
{
ArrayPool<byte>.Shared.Return(readBuffer);
ArrayPool<byte>.Shared.Return(writeBuffer);
}
}
public static async Task DownloadAssets(AssetIndexProperties assetIndexProperties, CancellationToken ct, bool force)
{
IOPath indexFilePath = Пути.GetAssetIndexFilePath(assetIndexProperties.id);
if (File.Exists(indexFilePath) && !force)
return;
IOPath indexFilePathTmp = indexFilePath + ".tmp";
if(File.Exists(indexFilePathTmp))
File.Delete(indexFilePathTmp);
// TODO: add something to Downloads ScrollList
Приложение.Логгер.LogInfo(nameof(DownloadAssets), $"started downloading asset index to '{indexFilePathTmp}'");
await DownloadFileHTTP(assetIndexProperties.url, indexFilePathTmp, null, ct);
Приложение.Логгер.LogInfo(nameof(DownloadAssets), "finished downloading asset index");
string indexFileText = File.ReadAllText(indexFilePathTmp);
AssetIndex assetIndex = JsonConvert.DeserializeObject<AssetIndex>(indexFileText)
?? throw new Exception($"can't deserialize asset index file '{indexFilePathTmp}'");
var assets = new List<KeyValuePair<string, AssetProperties>>();
HashSet<string> assetHashes = new HashSet<string>();
long totalSize = 0;
long currentSize = 0;
foreach (var pair in assetIndex.objects)
{
if (!assetHashes.Contains(pair.Value.hash))
{
assets.Add(pair);
assetHashes.Add(pair.Value.hash);
totalSize += pair.Value.size;
}
}
void AddBytesCountAtomic(ArraySegment<byte> chunk)
{
long chunkSize = chunk.Count;
Interlocked.Add(ref currentSize, chunkSize);
}
long prevSize = 0;
int timerDelay = 2000;
void ReportProgress()
{
// TODO: add something to Downloads ScrollList
long totalSizeM = totalSize/(1024*1024);
long currentSizeM = currentSize/(1024*1024);
long bytesPerSec = (currentSize - prevSize) / (timerDelay / 1000);
float KbytesPerSec = bytesPerSec / 1024f;
prevSize = currentSize;
Приложение.Логгер.LogDebug(nameof(DownloadAssets),
$"download progress {currentSizeM}Mb/{totalSizeM}Mb ({KbytesPerSec}Kb/s)");
}
using Timer timer = new Timer(true, timerDelay, ReportProgress);
timer.Start();
Приложение.Логгер.LogInfo(nameof(DownloadAssets), "started downloading assets");
int parallelDownloads = 32;
var tasks = new Task[parallelDownloads];
var currentlyDownloadingFileHashes = new string[parallelDownloads];
int i = 0;
foreach (var a in assets)
{
string hash = a.Value.hash;
string hashStart = hash.Substring(0, 2);
var assetUrl = $"{ASSET_SERVER_URL}/{hashStart}/{hash}";
IOPath assetFilePath = Path.Concat("assets", "objects", hashStart, hash);
Приложение.Логгер.LogDebug(nameof(DownloadAssets), $"downloading asset '{a.Key}' {hash}");
tasks[i] = DownloadFileHTTP(assetUrl, assetFilePath, AddBytesCountAtomic, ct);
currentlyDownloadingFileHashes[i] = hash;
if (++i == parallelDownloads)
{
await Task.WhenAll(tasks);
i = 0;
}
}
await Task.WhenAll(tasks);
timer.Stop();
timer.InvokeAction();
File.Move(indexFilePathTmp, indexFilePath, true);
Приложение.Логгер.LogInfo(nameof(DownloadAssets), "finished downloading assets");
}
private static async Task<List<RemoteVersionDescriptorProps>> GetRemoteVersionDescriptorsAsync()
{
List<RemoteVersionDescriptorProps> descriptors = new();
foreach (var url in VERSION_MANIFEST_URLS)
{
try
{
var manifestText = await http.GetStringAsync(url);
var catalog = JsonConvert.DeserializeObject<VersionCatalog>(manifestText);
if (catalog != null)
descriptors.AddRange(catalog.versions);
}
catch (Exception ex)
{
Приложение.Логгер.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;
}
public static async Task DownloadVersionFile(string url, IOPath filePath, bool force)
{
if(File.Exists(filePath) && !force)
return;
await DownloadFileHTTP(url, filePath);
}
public static async Task DownloadJava(JavaVersion javaVersion, IOPath path, bool force)
{
}
public static async Task DownloadLibraries(List<Library> libraries, IOPath librariesDir, bool force)
{
}
}