diff --git a/Mlaumcherb.Client.Avalonia/Mlaumcherb.Client.Avalonia.csproj b/Mlaumcherb.Client.Avalonia/Mlaumcherb.Client.Avalonia.csproj index ffdc793..4dd3ad2 100644 --- a/Mlaumcherb.Client.Avalonia/Mlaumcherb.Client.Avalonia.csproj +++ b/Mlaumcherb.Client.Avalonia/Mlaumcherb.Client.Avalonia.csproj @@ -18,12 +18,12 @@ - - - - - - + + + + + + diff --git a/Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpackRemoteProps.cs b/Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpackRemoteProps.cs new file mode 100644 index 0000000..af14e21 --- /dev/null +++ b/Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpackRemoteProps.cs @@ -0,0 +1,7 @@ +namespace Mlaumcherb.Client.Avalonia.классы; + +public class MyModpackRemoteProps +{ + [JsonRequired] public int format_version { get; set; } + [JsonRequired] public Artifact artifact { get; set; } = null!; +} \ No newline at end of file diff --git a/Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpack.cs b/Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpackV2.cs similarity index 59% rename from Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpack.cs rename to Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpackV2.cs index ac2d0da..a09313a 100644 --- a/Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpack.cs +++ b/Mlaumcherb.Client.Avalonia/классы/Пролетариат/MyModpackV2.cs @@ -2,37 +2,30 @@ namespace Mlaumcherb.Client.Avalonia.классы; -public class MyModpackRemoteProps +public class MyModpackV2 { - [JsonRequired] public int format_version { get; set; } - [JsonRequired] public Artifact artifact { get; set; } = null!; -} - -public class MyModpackV1 -{ - // 1 + // 2 [JsonRequired] public int format_version { get; set; } [JsonRequired] public string name { get; set; } = ""; - // zip archive with all files - [JsonRequired] public Artifact zip { get; set; } = null!; - /// relative_path, hash + /// relative_path, props // ReSharper disable once CollectionNeverUpdated.Global [JsonRequired] public Dictionary files { get; set; } = new(); public class FileProps { - [JsonRequired] public string sha1 { get; set; } = ""; + [JsonRequired] public Artifact artifact { get; set; } = null!; // disable hash validation, allowing users to edit this file public bool allow_edit { get; set; } // don't re-download file if it was deleted by user public bool optional { get; set; } } - /// relative, absolute - public Dictionary CheckFiles(IOPath basedir, bool checkHashes, bool downloadOptionalFiles) + + public record FileCheckResult(FileProps props, IOPath relativePath, IOPath absolutePath); + public List CheckFiles(IOPath basedir, bool checkHashes, bool downloadOptionalFiles) { - Dictionary unmatchedFiles = new(); + List unmatchedFiles = new(); IOPath launcherRoot = PathHelper.GetRootFullPath(); foreach (var p in files) { @@ -40,11 +33,12 @@ public class MyModpackV1 continue; IOPath relativePath = new IOPath(p.Key); + // libraries can be in modpacks, they are placed in global libraries directory var absolutePath = Path.Concat(relativePath.StartsWith("libraries") ? launcherRoot : basedir, relativePath); - if (!HashHelper.CheckFileSHA1(absolutePath, p.Value.sha1, checkHashes && !p.Value.allow_edit)) + if (!HashHelper.CheckFileSHA1(absolutePath, p.Value.artifact.sha1, checkHashes && !p.Value.allow_edit)) { - unmatchedFiles.Add(relativePath, absolutePath); + unmatchedFiles.Add(new FileCheckResult(p.Value, relativePath, absolutePath)); } } diff --git a/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/ModpackDownloadTaskFactory.cs b/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/ModpackDownloadTaskFactory.cs index 1444c1a..457267b 100644 --- a/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/ModpackDownloadTaskFactory.cs +++ b/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/ModpackDownloadTaskFactory.cs @@ -5,17 +5,15 @@ namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories; public class ModpackDownloadTaskFactory : INetworkTaskFactory { INetworkTaskFactory _implementationVersion; - private GameVersionDescriptor _descriptor; public ModpackDownloadTaskFactory(GameVersionDescriptor descriptor) { if(descriptor.modpack is null) throw new ArgumentNullException(nameof(descriptor.modpack)); - _descriptor = descriptor; _implementationVersion = descriptor.modpack.format_version switch { - 1 => new MyModpackV1DownloadTaskFactory(descriptor), + 2 => new MyModpackV2DownloadTaskFactory(descriptor), _ => throw new Exception($"Unknown Modpack format_version: {descriptor.modpack.format_version}") }; } diff --git a/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/MyModpackV1DownloadTaskFactory.cs b/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/MyModpackV2DownloadTaskFactory.cs similarity index 58% rename from Mlaumcherb.Client.Avalonia/сеть/TaskFactories/MyModpackV1DownloadTaskFactory.cs rename to Mlaumcherb.Client.Avalonia/сеть/TaskFactories/MyModpackV2DownloadTaskFactory.cs index 7dc7999..cbfed1e 100644 --- a/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/MyModpackV1DownloadTaskFactory.cs +++ b/Mlaumcherb.Client.Avalonia/сеть/TaskFactories/MyModpackV2DownloadTaskFactory.cs @@ -1,20 +1,20 @@ -using System.IO.Compression; +using System.Linq; using Mlaumcherb.Client.Avalonia.зримое; using Mlaumcherb.Client.Avalonia.классы; using Mlaumcherb.Client.Avalonia.холопы; namespace Mlaumcherb.Client.Avalonia.сеть.TaskFactories; -public class MyModpackV1DownloadTaskFactory : INetworkTaskFactory +public class MyModpackV2DownloadTaskFactory : INetworkTaskFactory { private readonly GameVersionDescriptor _descriptor; private IOPath _modpackDescriptorPath; private IOPath _versionDir; - private MyModpackV1? _modpack; + private MyModpackV2? _modpack; // relative, absolute - private Dictionary _filesToDosnload = new(); + private List _filesToDownload = new(); - public MyModpackV1DownloadTaskFactory(GameVersionDescriptor descriptor) + public MyModpackV2DownloadTaskFactory(GameVersionDescriptor descriptor) { _descriptor = descriptor; _modpackDescriptorPath = PathHelper.GetModpackDescriptorPath(_descriptor.id); @@ -26,7 +26,7 @@ public class MyModpackV1DownloadTaskFactory : INetworkTaskFactory if(_descriptor.modpack is null) throw new ArgumentNullException(nameof(_descriptor.modpack)); - (_modpack, bool didDownloadModpackDescriptor) = await NetworkHelper.ReadOrDownloadAndDeserialize( + (_modpack, bool didDownloadModpackDescriptor) = await NetworkHelper.ReadOrDownloadAndDeserialize( _modpackDescriptorPath, _descriptor.modpack.artifact.url, _descriptor.modpack.artifact.sha1, @@ -36,12 +36,12 @@ public class MyModpackV1DownloadTaskFactory : INetworkTaskFactory $"{_modpack.format_version} != {_descriptor.modpack.format_version}"); NetworkTask? networkTask = null; - _filesToDosnload = _modpack.CheckFiles(_versionDir, checkHashes, didDownloadModpackDescriptor); - if(_filesToDosnload.Count > 0) + _filesToDownload = _modpack.CheckFiles(_versionDir, checkHashes, didDownloadModpackDescriptor); + if(_filesToDownload.Count > 0) { networkTask = new NetworkTask( $"modpack '{_modpack.name}'", - _modpack.zip.size, + _filesToDownload.Sum(f => f.props.artifact.size), Download ); } @@ -52,23 +52,19 @@ public class MyModpackV1DownloadTaskFactory : INetworkTaskFactory private async Task Download(NetworkProgressReporter pr, CancellationToken ct) { LauncherApp.Logger.LogInfo(nameof(NetworkHelper), $"started downloading modpack '{_modpack!.name}'"); - if(string.IsNullOrEmpty(_modpack.zip.url)) - throw new Exception($"modpack '{_modpack.name}' doesn't have a url to download"); - var _archivePath = Path.Concat(PathHelper.GetCacheDir(), "modpacks", _modpack.name + ".zip"); - await NetworkHelper.DownloadFile(_modpack.zip.url, _archivePath, ct, pr.AddBytesCount); - - await using var zipf = File.OpenRead(_archivePath); - using var archive = new ZipArchive(zipf); - foreach (var entry in archive.Entries) + ParallelOptions opt = new() { - IOPath relativePath = new(entry.FullName); - if(_filesToDosnload.TryGetValue(relativePath, out var absolutePath)) - { - Directory.Create(absolutePath.ParentDir()); - entry.ExtractToFile(absolutePath.ToString(), true); - } - } + MaxDegreeOfParallelism = LauncherApp.Config.max_parallel_downloads, + CancellationToken = ct + }; + await Parallel.ForEachAsync(_filesToDownload, opt, async (f, _ct) => + { + LauncherApp.Logger.LogDebug(nameof(NetworkHelper), $"downloading file '{f.relativePath}'"); + if(string.IsNullOrEmpty(f.props.artifact.url)) + throw new Exception($"file '{f.relativePath}' doesn't have a url to download"); + await NetworkHelper.DownloadFile(f.props.artifact.url, f.absolutePath, _ct, pr.AddBytesCount); + }); LauncherApp.Logger.LogInfo(nameof(NetworkHelper), $"finished downloading modpack '{_modpack.name}'"); }