191 lines
7.6 KiB
C#
191 lines
7.6 KiB
C#
using System.Buffers;
|
||
using System.Net.Http;
|
||
using Млаумчерб.Клиент.классы;
|
||
using Timer = DTLib.Timer;
|
||
|
||
namespace Млаумчерб.Клиент;
|
||
|
||
public static class Network
|
||
{
|
||
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(Network), 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)
|
||
{
|
||
|
||
}
|
||
} |