From 612976dfe61b3cd2005140a4914ccbf57e28744b Mon Sep 17 00:00:00 2001 From: Timerix Date: Mon, 4 Nov 2024 00:41:25 +0500 Subject: [PATCH] its downloading!!! --- .gitignore | 1 + nuget.config | 7 -- Млаумчерб.Клиент/LZMACompressor.cs | 26 ++++ Млаумчерб.Клиент/Главне.cs | 3 +- Млаумчерб.Клиент/Игра.cs | 62 ++++++---- Млаумчерб.Клиент/Логи.cs | 6 +- Млаумчерб.Клиент/Млаумчерб.Клиент.csproj | 24 ++-- Млаумчерб.Клиент/Настройки.cs | 14 +-- Млаумчерб.Клиент/Ошибки.cs | 2 +- .../видимое/DownloadItemView.axaml | 20 ---- .../видимое/DownloadItemView.axaml.cs | 42 ------- .../зримое/DownloadItemView.axaml | 26 ++++ .../зримое/DownloadItemView.axaml.cs | 66 +++++++++++ Млаумчерб.Клиент/зримое/LogMessageView.axaml | 13 ++ .../зримое/LogMessageView.axaml.cs | 24 ++++ .../{видимое => зримое}/VersionItemView.axaml | 2 +- .../{видимое => зримое}/VersionItemView.axaml.cs | 2 +- .../{видимое => зримое}/Окне.axaml | 92 ++++++++------ .../{видимое => зримое}/Окне.axaml.cs | 112 +++++++++++------- .../{видимое => зримое}/Приложение.axaml | 2 +- .../{видимое => зримое}/Приложение.axaml.cs | 2 +- .../классы/GameVersionDescriptor.cs | 2 +- Млаумчерб.Клиент/классы/GameVersionProps.cs | 31 ++++- Млаумчерб.Клиент/классы/JavaVersionCatalog.cs | 68 +++++++++-- Млаумчерб.Клиент/классы/Libraries.cs | 20 +++- Млаумчерб.Клиент/классы/Буржуазия.cs | 2 +- Млаумчерб.Клиент/классы/Пути.cs | 8 +- .../сеть/NetworkProgressReporter.cs | 7 +- Млаумчерб.Клиент/сеть/NetworkTask.cs | 12 +- .../сеть/NetworkTaskFactories/AssetsDownloadTaskFactory.cs | 40 +++++-- .../сеть/NetworkTaskFactories/JavaDownloadTaskFactory.cs | 96 ++++++++++++--- .../NetworkTaskFactories/LibrariesDownloadTaskFactory.cs | 15 ++- .../NetworkTaskFactories/VersionFileDownloadTaskFactory.cs | 4 +- Млаумчерб.Клиент/сеть/Сеть.cs | 77 ++++++------ млаумчерб.sln | 2 +- 35 files changed, 635 insertions(+), 297 deletions(-) delete mode 100644 nuget.config create mode 100644 Млаумчерб.Клиент/LZMACompressor.cs delete mode 100644 Млаумчерб.Клиент/видимое/DownloadItemView.axaml delete mode 100644 Млаумчерб.Клиент/видимое/DownloadItemView.axaml.cs create mode 100644 Млаумчерб.Клиент/зримое/DownloadItemView.axaml create mode 100644 Млаумчерб.Клиент/зримое/DownloadItemView.axaml.cs create mode 100644 Млаумчерб.Клиент/зримое/LogMessageView.axaml create mode 100644 Млаумчерб.Клиент/зримое/LogMessageView.axaml.cs rename Млаумчерб.Клиент/{видимое => зримое}/VersionItemView.axaml (83%) rename Млаумчерб.Клиент/{видимое => зримое}/VersionItemView.axaml.cs (90%) rename Млаумчерб.Клиент/{видимое => зримое}/Окне.axaml (64%) rename Млаумчерб.Клиент/{видимое => зримое}/Окне.axaml.cs (63%) rename Млаумчерб.Клиент/{видимое => зримое}/Приложение.axaml (94%) rename Млаумчерб.Клиент/{видимое => зримое}/Приложение.axaml.cs (90%) diff --git a/.gitignore b/.gitignore index 62faaac..26d6466 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ .idea/ .editorconfig *.user +*.DotSettings #backups .old*/ \ No newline at end of file diff --git a/nuget.config b/nuget.config deleted file mode 100644 index d9602b1..0000000 --- a/nuget.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Млаумчерб.Клиент/LZMACompressor.cs b/Млаумчерб.Клиент/LZMACompressor.cs new file mode 100644 index 0000000..123bb34 --- /dev/null +++ b/Млаумчерб.Клиент/LZMACompressor.cs @@ -0,0 +1,26 @@ +namespace Млаумчерб.Клиент; + +/// +/// https://gist.github.com/ststeiger/cb9750664952f775a341 +/// +public static class LZMACompressor +{ + public static void Decompress(Stream inputStream, Stream outputStream) + { + var decoder = new SevenZip.Compression.LZMA.Decoder(); + var properties = new byte[5]; + // Read decoder properties + if (inputStream.Read(properties, 0, 5) != 5) + throw new Exception("lzma stream is too short"); + decoder.SetDecoderProperties(properties); + + // Read decompressed data size + var fileLengthBytes = new byte[8]; + if(inputStream.Read(fileLengthBytes, 0, 8) != 8) + throw new Exception("lzma stream is too short"); + long fileLength = BitConverter.ToInt64(fileLengthBytes, 0); + + // Decode + decoder.Code(inputStream, outputStream, -1, fileLength, null); + } +} \ No newline at end of file diff --git a/Млаумчерб.Клиент/Главне.cs b/Млаумчерб.Клиент/Главне.cs index e055cec..b395293 100644 --- a/Млаумчерб.Клиент/Главне.cs +++ b/Млаумчерб.Клиент/Главне.cs @@ -6,6 +6,7 @@ global using System.Text; global using System.Threading; global using System.Threading.Tasks; global using Newtonsoft.Json; +global using DTLib; global using DTLib.Logging; global using DTLib.Filesystem; global using File = DTLib.Filesystem.File; @@ -13,7 +14,7 @@ global using Directory = DTLib.Filesystem.Directory; global using Path = DTLib.Filesystem.Path; using System.Globalization; using Avalonia; -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; namespace Млаумчерб.Клиент; diff --git a/Млаумчерб.Клиент/Игра.cs b/Млаумчерб.Клиент/Игра.cs index f11357a..df98a8f 100644 --- a/Млаумчерб.Клиент/Игра.cs +++ b/Млаумчерб.Клиент/Игра.cs @@ -1,6 +1,7 @@ -using CliWrap; +using System.Linq; +using CliWrap; using DTLib.Extensions; -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; using Млаумчерб.Клиент.классы; using Млаумчерб.Клиент.сеть; using Млаумчерб.Клиент.сеть.NetworkTaskFactories; @@ -25,15 +26,24 @@ public class GameVersion public static async Task> GetAllVersionsAsync() { - var propsList = new List(); - foreach (IOPath f in Directory.GetFiles(GetVersionDescriptorDir())) + var propsSet = new HashSet(); + + // local descriptors + foreach (IOPath f in Directory.GetFiles(GetVersionDescriptorsDir())) { string name = GetVersionDescriptorName(f); - propsList.Add(new GameVersionProps(name, null, f)); + propsSet.Add(new GameVersionProps(name, null, f)); } - - var remoteVersions = await Сеть.GetDownloadableVersions(); - propsList.AddRange(remoteVersions); + + // remote non-duplicating versions + foreach (var removeVersion in await Сеть.GetDownloadableVersions()) + { + propsSet.Add(removeVersion); + } + + // reverse sort + var propsList = propsSet.ToList(); + propsList.Sort((a, b) => b.CompareTo(a)); return propsList; } @@ -44,7 +54,7 @@ public class GameVersion if (props.RemoteDescriptorUrl is null) throw new NullReferenceException("can't download game version descriptor '" + props.Name + "', because RemoteDescriptorUrl is null"); - await Сеть.DownloadFileHTTP(props.RemoteDescriptorUrl, props.LocalDescriptorPath); + await Сеть.DownloadFile(props.RemoteDescriptorUrl, props.LocalDescriptorPath); } return new GameVersion(props); @@ -63,44 +73,44 @@ public class GameVersion JavaExecutableFilePath = GetJavaExecutablePath(descriptor.javaVersion.component); } - public async Task> CreateUpdateTasksAsync(bool checkHashes) + public async Task UpdateFiles(bool checkHashes, Action networkTaskCreatedCallback) { + Приложение.Логгер.LogInfo(nameof(GameVersion), $"started updating version {Name}"); + List taskFactories = [ new AssetsDownloadTaskFactory(descriptor), new LibrariesDownloadTaskFactory(descriptor, libraries), new VersionFileDownloadTaskFactory(descriptor), ]; - /*if(Приложение.Настройки.скачать_жабу) + if(Приложение.Настройки.скачивать_жабу) { taskFactories.Add(new JavaDownloadTaskFactory(descriptor)); - }*/ + } + //TODO: modpack /*if (modpack != null) { taskFactories.Add(new ModpackDownloadTaskFactory(modpack)); }*/ - List tasks = new(); + var networkTasks = new List(); for (int i = 0; i < taskFactories.Count; i++) { var nt = await taskFactories[i].CreateAsync(checkHashes); - if (nt != null) - tasks.Add(nt); + if (nt != null) + { + networkTasks.Add(nt); + networkTaskCreatedCallback.Invoke(nt); + } } - if (tasks.Count == 0) + foreach (var nt in networkTasks) { - _props.IsDownloaded = true; + await nt.StartAsync(); } - else - { - tasks[^1].OnStop += status => - { - if (status == NetworkTask.Status.Completed) - _props.IsDownloaded = true; - }; - } - return tasks; + + _props.IsDownloaded = true; + Приложение.Логгер.LogInfo(nameof(GameVersion), $"finished updating version {Name}"); } public async Task Launch() diff --git a/Млаумчерб.Клиент/Логи.cs b/Млаумчерб.Клиент/Логи.cs index ed58b09..87ca487 100644 --- a/Млаумчерб.Клиент/Логи.cs +++ b/Млаумчерб.Клиент/Логи.cs @@ -24,7 +24,9 @@ public class LauncherLogger : ILogger #endif } - public delegate void LogHandler(string context, LogSeverity severity, object message, ILogFormat format); + public record LogMessage(string context, LogSeverity severity, object message, ILogFormat format); + + public delegate void LogHandler(LogMessage msg); public event LogHandler? OnLogMessage; public void Log(string context, LogSeverity severity, object message, ILogFormat format) @@ -39,7 +41,7 @@ public class LauncherLogger : ILogger _ => throw new ArgumentOutOfRangeException(nameof(severity), severity, null) }; if(isEnabled) - OnLogMessage?.Invoke(context, severity, message, format); + OnLogMessage?.Invoke(new(context, severity, message, format)); } public void Dispose() diff --git a/Млаумчерб.Клиент/Млаумчерб.Клиент.csproj b/Млаумчерб.Клиент/Млаумчерб.Клиент.csproj index 581ae2d..4383775 100644 --- a/Млаумчерб.Клиент/Млаумчерб.Клиент.csproj +++ b/Млаумчерб.Клиент/Млаумчерб.Клиент.csproj @@ -16,16 +16,16 @@ - - - - - - - - + + + + + + + + + - @@ -33,15 +33,15 @@ - + VersionItemView.axaml Code - + Окне.axaml Code - + Приложение.axaml Code diff --git a/Млаумчерб.Клиент/Настройки.cs b/Млаумчерб.Клиент/Настройки.cs index b302fdf..b38672f 100644 --- a/Млаумчерб.Клиент/Настройки.cs +++ b/Млаумчерб.Клиент/Настройки.cs @@ -1,5 +1,4 @@ -using DTLib.Extensions; -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; namespace Млаумчерб.Клиент; @@ -7,12 +6,11 @@ public record Настройки { public string имя_пользователя { get; set; } = ""; public int выделенная_память_мб { get; set; } = 4096; - public bool открывать_на_весь_экран { get; set; } public string путь_к_кубачу { get; set; } = "."; - public bool скачать_жабу { get; set; } = true; + public bool запускать_полноэкранное { get; set; } = false; + public bool скачивать_жабу { get; set; } = true; public string? последняя_запущенная_версия { get; set; } - - [JsonIgnore] private Stream? fileWriteStream; + public int максимум_параллельных_загрузок { get; set; } = 16; public static Настройки ЗагрузитьИзФайла(string имя_файла = "млаумчерб.настройки") { @@ -43,10 +41,8 @@ public record Настройки { //TODO: file backup and restore Приложение.Логгер.LogDebug(nameof(Настройки), $"настройки сохраняются в файл '{имя_файла}'"); - fileWriteStream ??= File.OpenWrite(имя_файла); var текст = JsonConvert.SerializeObject(this, Formatting.Indented); - fileWriteStream.Seek(0, SeekOrigin.Begin); - fileWriteStream.FluentWriteString(текст).Flush(); + File.WriteAllText(имя_файла, текст); Приложение.Логгер.LogDebug(nameof(Настройки), $"настройки сохранены: {текст}"); } } \ No newline at end of file diff --git a/Млаумчерб.Клиент/Ошибки.cs b/Млаумчерб.Клиент/Ошибки.cs index 3bf5e4a..09b19c1 100644 --- a/Млаумчерб.Клиент/Ошибки.cs +++ b/Млаумчерб.Клиент/Ошибки.cs @@ -4,7 +4,7 @@ using MsBox.Avalonia; using MsBox.Avalonia.Dto; using MsBox.Avalonia.Enums; using MsBox.Avalonia.Models; -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; namespace Млаумчерб.Клиент; diff --git a/Млаумчерб.Клиент/видимое/DownloadItemView.axaml b/Млаумчерб.Клиент/видимое/DownloadItemView.axaml deleted file mode 100644 index a5c5088..0000000 --- a/Млаумчерб.Клиент/видимое/DownloadItemView.axaml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - diff --git a/Млаумчерб.Клиент/видимое/DownloadItemView.axaml.cs b/Млаумчерб.Клиент/видимое/DownloadItemView.axaml.cs deleted file mode 100644 index 39bf4d9..0000000 --- a/Млаумчерб.Клиент/видимое/DownloadItemView.axaml.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Interactivity; -using Avalonia.Threading; -using Млаумчерб.Клиент.сеть; - -namespace Млаумчерб.Клиент.видимое; - -public partial class DownloadTaskView : UserControl -{ - private readonly NetworkTask _task; - private readonly Action _removeFromList; - - - public DownloadTaskView() - { - throw new NotImplementedException(); - } - - public DownloadTaskView(NetworkTask task, Action removeFromList) - { - _task = task; - _removeFromList = removeFromList; - InitializeComponent(); - NameText.Text = task.Name; - task.OnProgress += ReportProgress; - } - - - void ReportProgress(DownloadProgress progress) - { - Dispatcher.UIThread.Invoke(() => - { - DownloadedProgressText.Text = progress.ToString(); - }); - } - - private void RemoveFromList(object? sender, RoutedEventArgs e) - { - _task.Cancel(); - Dispatcher.UIThread.Invoke(() => _removeFromList.Invoke(this)); - } -} diff --git a/Млаумчерб.Клиент/зримое/DownloadItemView.axaml b/Млаумчерб.Клиент/зримое/DownloadItemView.axaml new file mode 100644 index 0000000..71ee5e5 --- /dev/null +++ b/Млаумчерб.Клиент/зримое/DownloadItemView.axaml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/Млаумчерб.Клиент/зримое/DownloadItemView.axaml.cs b/Млаумчерб.Клиент/зримое/DownloadItemView.axaml.cs new file mode 100644 index 0000000..e0abe11 --- /dev/null +++ b/Млаумчерб.Клиент/зримое/DownloadItemView.axaml.cs @@ -0,0 +1,66 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Млаумчерб.Клиент.сеть; + +namespace Млаумчерб.Клиент.зримое; + +public partial class NetworkTaskView : UserControl +{ + public readonly NetworkTask Task; + private readonly Action _removeFromList; + + + public NetworkTaskView() + { + throw new Exception(); + } + + public NetworkTaskView(NetworkTask task, Action removeFromList) + { + Task = task; + _removeFromList = removeFromList; + InitializeComponent(); + NameText.Text = task.Name; + StatusText.Text = task.DownloadStatus.ToString(); + task.OnStart += OnTaskOnStart; + task.OnProgress += ReportProgress; + task.OnStop += OnTaskStop; + } + + private void OnTaskOnStart() + { + Dispatcher.UIThread.Invoke(() => + { + StatusText.Text = Task.DownloadStatus.ToString(); + }); + } + + private void OnTaskStop(NetworkTask.Status status) + { + Dispatcher.UIThread.Invoke(() => + { + StatusText.Text = status.ToString(); + if(!string.IsNullOrEmpty(ProgressText.Text)) + { + int speedIndex = ProgressText.Text.IndexOf('('); + if(speedIndex > 0) + ProgressText.Text = ProgressText.Text.Remove(speedIndex); + } + }); + } + + void ReportProgress(DownloadProgress progress) + { + Dispatcher.UIThread.Invoke(() => + { + ProgressText.Text = progress.ToString(); + }); + } + + private void RemoveFromList(object? sender, RoutedEventArgs e) + { + Task.Cancel(); + Dispatcher.UIThread.Invoke(() => _removeFromList.Invoke(this)); + } +} diff --git a/Млаумчерб.Клиент/зримое/LogMessageView.axaml b/Млаумчерб.Клиент/зримое/LogMessageView.axaml new file mode 100644 index 0000000..a5c3762 --- /dev/null +++ b/Млаумчерб.Клиент/зримое/LogMessageView.axaml @@ -0,0 +1,13 @@ + + + diff --git a/Млаумчерб.Клиент/зримое/LogMessageView.axaml.cs b/Млаумчерб.Клиент/зримое/LogMessageView.axaml.cs new file mode 100644 index 0000000..73c5259 --- /dev/null +++ b/Млаумчерб.Клиент/зримое/LogMessageView.axaml.cs @@ -0,0 +1,24 @@ +using Avalonia.Controls; +using DTLib; + +namespace Млаумчерб.Клиент.зримое; + +public partial class LogMessageView : UserControl +{ + public static ILogFormat ShortLogFormat = new DefaultLogFormat + { + TimeStampFormat = MyTimeFormat.TimeOnly, + PrintContext = false + }; + + public LogMessageView() + { + throw new Exception(); + } + + public LogMessageView(LauncherLogger.LogMessage m) + { + InitializeComponent(); + ContentTextBox.Text = ShortLogFormat.CreateMessage(m.context, m.severity, m.message); + } +} \ No newline at end of file diff --git a/Млаумчерб.Клиент/видимое/VersionItemView.axaml b/Млаумчерб.Клиент/зримое/VersionItemView.axaml similarity index 83% rename from Млаумчерб.Клиент/видимое/VersionItemView.axaml rename to Млаумчерб.Клиент/зримое/VersionItemView.axaml index 42bfc27..2a629dc 100644 --- a/Млаумчерб.Клиент/видимое/VersionItemView.axaml +++ b/Млаумчерб.Клиент/зримое/VersionItemView.axaml @@ -4,6 +4,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Млаумчерб.Клиент" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="Млаумчерб.Клиент.видимое.VersionItemView"> + x:Class="Млаумчерб.Клиент.зримое.VersionItemView"> diff --git a/Млаумчерб.Клиент/видимое/VersionItemView.axaml.cs b/Млаумчерб.Клиент/зримое/VersionItemView.axaml.cs similarity index 90% rename from Млаумчерб.Клиент/видимое/VersionItemView.axaml.cs rename to Млаумчерб.Клиент/зримое/VersionItemView.axaml.cs index 3aa36be..6ec7da5 100644 --- a/Млаумчерб.Клиент/видимое/VersionItemView.axaml.cs +++ b/Млаумчерб.Клиент/зримое/VersionItemView.axaml.cs @@ -2,7 +2,7 @@ using Avalonia.Media; using Млаумчерб.Клиент.классы; -namespace Млаумчерб.Клиент.видимое; +namespace Млаумчерб.Клиент.зримое; public partial class VersionItemView : ListBoxItem { diff --git a/Млаумчерб.Клиент/видимое/Окне.axaml b/Млаумчерб.Клиент/зримое/Окне.axaml similarity index 64% rename from Млаумчерб.Клиент/видимое/Окне.axaml rename to Млаумчерб.Клиент/зримое/Окне.axaml index 6b5f528..7ac5843 100644 --- a/Млаумчерб.Клиент/видимое/Окне.axaml +++ b/Млаумчерб.Клиент/зримое/Окне.axaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:gif="clr-namespace:Avalonia.Labs.Gif;assembly=Avalonia.Labs.Gif" xmlns:local="clr-namespace:Млаумчерб" - x:Class="Млаумчерб.Клиент.видимое.Окне" + x:Class="Млаумчерб.Клиент.зримое.Окне" Name="window" Title="млаумчерб" Icon="avares://млаумчерб/капитал/кубе.ico" @@ -17,29 +17,8 @@ * 300 * - - - 30 * - - - Лог - - - - - - - + + @@ -67,14 +46,17 @@ - Запустить полноэкранное + Запускать полноэкранное - - Проверить файлы игры + Проверять файлы игры + + + Скачивать джаву - + + + + + + + + + - 30 * + 36 * - - Загрузки - + + + Загрузки + + + - + @@ -106,7 +126,7 @@ - + diff --git a/Млаумчерб.Клиент/видимое/Окне.axaml.cs b/Млаумчерб.Клиент/зримое/Окне.axaml.cs similarity index 63% rename from Млаумчерб.Клиент/видимое/Окне.axaml.cs rename to Млаумчерб.Клиент/зримое/Окне.axaml.cs index e4dc2cb..f139f10 100644 --- a/Млаумчерб.Клиент/видимое/Окне.axaml.cs +++ b/Млаумчерб.Клиент/зримое/Окне.axaml.cs @@ -6,7 +6,7 @@ using Avalonia.Platform.Storage; using Avalonia.Threading; using Млаумчерб.Клиент.классы; -namespace Млаумчерб.Клиент.видимое; +namespace Млаумчерб.Клиент.зримое; public partial class Окне : Window { @@ -46,6 +46,16 @@ public partial class Окне : Window set => SetValue(CheckGameFilesProperty, value); } + + public static readonly StyledProperty EnableJavaDownloadProperty = + AvaloniaProperty.Register<Окне, bool>(nameof(EnableJavaDownload), + defaultBindingMode: BindingMode.TwoWay, defaultValue: true); + public bool EnableJavaDownload + { + get => GetValue(EnableJavaDownloadProperty); + set => SetValue(EnableJavaDownloadProperty, value); + } + public Окне() { InitializeComponent(); @@ -55,34 +65,14 @@ public partial class Окне : Window { try { - Приложение.Логгер.OnLogMessage += (context, severity, message, format) => - { - if(severity == LogSeverity.Debug) - return; - - StringBuilder b = new(); - b.Append(DateTime.Now.ToString("[HH:mm:ss][")); - b.Append(severity); - b.Append("]: "); - b.Append(message); - b.Append('\n'); - Dispatcher.UIThread.Invoke(() => - { - double offsetFromBottom = LogScrollViewer.Extent.Height - - LogScrollViewer.Offset.Y - - LogScrollViewer.Viewport.Height; - bool is_scrolled_to_end = offsetFromBottom < 20.0; // scrolled less then one line up - LogTextBox.Text += b.ToString(); - if (is_scrolled_to_end) - LogScrollViewer.ScrollToEnd(); - }); - }; - + Приложение.Логгер.OnLogMessage += GuiLogMessage; + Username = Приложение.Настройки.имя_пользователя; MemoryLimit = Приложение.Настройки.выделенная_память_мб; - Fullscreen = Приложение.Настройки.открывать_на_весь_экран; - - Directory.Create(Пути.GetVersionDescriptorDir()); + Fullscreen = Приложение.Настройки.запускать_полноэкранное; + EnableJavaDownload = Приложение.Настройки.скачивать_жабу; + + Directory.Create(Пути.GetVersionDescriptorsDir()); VersionComboBox.SelectedIndex = 0; VersionComboBox.IsEnabled = false; var versions = await GameVersion.GetAllVersionsAsync(); @@ -91,8 +81,8 @@ public partial class Окне : Window foreach (var p in versions) { VersionComboBox.Items.Add(new VersionItemView(p)); - if (Приложение.Настройки.последняя_запущенная_версия != null && - p.Name == Приложение.Настройки.последняя_запущенная_версия) + if (Приложение.Настройки.последняя_запущенная_версия != null && + p.Name == Приложение.Настройки.последняя_запущенная_версия) VersionComboBox.SelectedIndex = VersionComboBox.Items.Count - 1; } VersionComboBox.IsEnabled = true; @@ -104,38 +94,62 @@ public partial class Окне : Window } } + private void GuiLogMessage(LauncherLogger.LogMessage msg) + { + if (msg.severity == LogSeverity.Debug) return; + + Dispatcher.UIThread.Invoke(() => + { + double offsetFromBottom = LogScrollViewer.Extent.Height - LogScrollViewer.Offset.Y - LogScrollViewer.Viewport.Height; + bool is_scrolled_to_end = offsetFromBottom < 20.0; // scrolled less then one line up + LogPanel.Children.Add(new LogMessageView(msg)); + if (is_scrolled_to_end) LogScrollViewer.ScrollToEnd(); + }); + } + private async void Запуск(object? sender, RoutedEventArgs e) { try { + Dispatcher.UIThread.Invoke(() => LaunchButton.IsEnabled = false); + var selectedVersionView = (VersionItemView?)VersionComboBox.SelectedItem; var selectedVersion = selectedVersionView?.Props; Приложение.Настройки.последняя_запущенная_версия = selectedVersion?.Name; Приложение.Настройки.имя_пользователя = Username; Приложение.Настройки.выделенная_память_мб = MemoryLimit; - Приложение.Настройки.открывать_на_весь_экран = Fullscreen; + Приложение.Настройки.запускать_полноэкранное = Fullscreen; + Приложение.Настройки.скачивать_жабу = EnableJavaDownload; Приложение.Настройки.СохранитьВФайл(); if (selectedVersion == null) return; var v = await GameVersion.CreateFromPropsAsync(selectedVersion); - var updateTasks = await v.CreateUpdateTasksAsync(CheckGameFiles); - foreach (var t in updateTasks) - { - var updateTask = t.StartAsync(); - Dispatcher.UIThread.Invoke(() => + await v.UpdateFiles(CheckGameFiles, nt => { - var view = new DownloadTaskView(t, view => DownloadsPanel.Children.Remove(view)); - DownloadsPanel.Children.Add(view); - }); - await updateTask; - } - Dispatcher.UIThread.Invoke(() => CheckGameFiles = false); + Dispatcher.UIThread.Invoke(() => + { + DownloadsPanel.Children.Add(new NetworkTaskView(nt, + ntv => DownloadsPanel.Children.Remove(ntv))); + }); + } + ); + Dispatcher.UIThread.Invoke(() => + { + CheckGameFiles = false; + }); } catch (Exception ex) { Ошибки.ПоказатьСообщение(nameof(Окне), ex); } + finally + { + Dispatcher.UIThread.Invoke(() => + { + LaunchButton.IsEnabled = true; + }); + } } private void ОткрытьПапкуЛаунчера(object? s, RoutedEventArgs e) @@ -176,4 +190,20 @@ public partial class Окне : Window Ошибки.ПоказатьСообщение(nameof(Окне), ex); } } + + private void ClearLogPanel(object? sender, RoutedEventArgs e) + { + LogPanel.Children.Clear(); + } + + + private void ClearDownloadsPanel(object? sender, RoutedEventArgs e) + { + foreach (var control in DownloadsPanel.Children) + { + var nt = (NetworkTaskView)control; + nt.Task.Cancel(); + } + DownloadsPanel.Children.Clear(); + } } \ No newline at end of file diff --git a/Млаумчерб.Клиент/видимое/Приложение.axaml b/Млаумчерб.Клиент/зримое/Приложение.axaml similarity index 94% rename from Млаумчерб.Клиент/видимое/Приложение.axaml rename to Млаумчерб.Клиент/зримое/Приложение.axaml index 9c71f16..93ed858 100644 --- a/Млаумчерб.Клиент/видимое/Приложение.axaml +++ b/Млаумчерб.Клиент/зримое/Приложение.axaml @@ -1,6 +1,6 @@ diff --git a/Млаумчерб.Клиент/видимое/Приложение.axaml.cs b/Млаумчерб.Клиент/зримое/Приложение.axaml.cs similarity index 90% rename from Млаумчерб.Клиент/видимое/Приложение.axaml.cs rename to Млаумчерб.Клиент/зримое/Приложение.axaml.cs index 744411e..45e96ba 100644 --- a/Млаумчерб.Клиент/видимое/Приложение.axaml.cs +++ b/Млаумчерб.Клиент/зримое/Приложение.axaml.cs @@ -2,7 +2,7 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -namespace Млаумчерб.Клиент.видимое; +namespace Млаумчерб.Клиент.зримое; public class Приложение : Application { diff --git a/Млаумчерб.Клиент/классы/GameVersionDescriptor.cs b/Млаумчерб.Клиент/классы/GameVersionDescriptor.cs index 56e83d9..761a592 100644 --- a/Млаумчерб.Клиент/классы/GameVersionDescriptor.cs +++ b/Млаумчерб.Клиент/классы/GameVersionDescriptor.cs @@ -11,10 +11,10 @@ public class GameVersionDescriptor [JsonRequired] public string type { get; set; } = ""; [JsonRequired] public string mainClass { get; set; } = ""; [JsonRequired] public Downloads downloads { get; set; } = null!; - [JsonRequired] public JavaVersion javaVersion { get; set; } = null!; [JsonRequired] public List libraries { get; set; } = null!; [JsonRequired] public AssetIndexProperties assetIndex { get; set; } = null!; [JsonRequired] public string assets { get; set; } = ""; + public JavaVersion javaVersion { get; set; } = new() { component = "jre-legacy", majorVersion = 8 }; public string? minecraftArguments { get; set; } public ArgumentsNew? arguments { get; set; } } diff --git a/Млаумчерб.Клиент/классы/GameVersionProps.cs b/Млаумчерб.Клиент/классы/GameVersionProps.cs index a508f16..e9e1494 100644 --- a/Млаумчерб.Клиент/классы/GameVersionProps.cs +++ b/Млаумчерб.Клиент/классы/GameVersionProps.cs @@ -1,6 +1,6 @@ namespace Млаумчерб.Клиент.классы; -public class GameVersionProps +public class GameVersionProps : IComparable, IEquatable { public string Name { get; } public IOPath LocalDescriptorPath { get; } @@ -31,4 +31,33 @@ public class GameVersionProps this(name, url, Пути.GetVersionDescriptorPath(name)) { } public override string ToString() => Name; + + public override int GetHashCode() => Name.GetHashCode(); + + public int CompareTo(GameVersionProps? other) + { + if (ReferenceEquals(this, other)) return 0; + if (other is null) return 1; + + if (Version.TryParse(Name, out var version1) && Version.TryParse(other.Name, out var version2)) + { + return version1.CompareTo(version2); + } + + return String.Compare(Name, other.Name, StringComparison.InvariantCulture); + } + + public bool Equals(GameVersionProps? other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return Name == other.Name; + } + + public override bool Equals(object? obj) + { + if (obj is GameVersionProps other) return Equals(other); + if (ReferenceEquals(this, obj)) return true; + return false; + } } diff --git a/Млаумчерб.Клиент/классы/JavaVersionCatalog.cs b/Млаумчерб.Клиент/классы/JavaVersionCatalog.cs index c2c6380..af18f96 100644 --- a/Млаумчерб.Клиент/классы/JavaVersionCatalog.cs +++ b/Млаумчерб.Клиент/классы/JavaVersionCatalog.cs @@ -1,23 +1,77 @@ -namespace Млаумчерб.Клиент.классы; +using System.Runtime.InteropServices; + +namespace Млаумчерб.Клиент.классы; public class JavaVersionCatalog { - + [JsonProperty("linux")] + public Dictionary? linux_x86 { get; set; } + [JsonProperty("linux-i386")] + public Dictionary? linux_x64 { get; set; } + [JsonProperty("mac-os")] + public Dictionary? osx_x64 { get; set; } + [JsonProperty("mac-os-arm64")] + public Dictionary? osx_arm64 { get; set; } + [JsonProperty("windows-arm64")] + public Dictionary? windows_arm64 { get; set; } + [JsonProperty("windows-x64")] + public Dictionary? windows_x64 { get; set; } + [JsonProperty("windows-x86")] + public Dictionary? windows_x86 { get; set; } + + public JavaVersionProps GetVersionProps(JavaVersion version) + { + var arch = RuntimeInformation.OSArchitecture; + Dictionary? propsDict = null; + switch (arch) + { + case Architecture.X86: + if (OperatingSystem.IsWindows()) + propsDict = windows_x86; + else if (OperatingSystem.IsLinux()) + propsDict = linux_x86; + break; + case Architecture.X64: + if (OperatingSystem.IsWindows()) + propsDict = windows_x64; + else if (OperatingSystem.IsLinux()) + propsDict = linux_x64; + else if (OperatingSystem.IsMacOS()) + propsDict = osx_x64; + break; + case Architecture.Arm64: + if (OperatingSystem.IsWindows()) + propsDict = windows_arm64; + else if (OperatingSystem.IsMacOS()) + propsDict = osx_arm64; + break; + } + + if (propsDict != null && propsDict.TryGetValue(version.component, out var props_array)) + { + if (props_array.Length != 0) + return props_array[0]; + } + + throw new PlatformNotSupportedException($"Can't download java {version.majorVersion} for your operating system. " + + $"Download it manually to directory {Пути.GetJavaRuntimeDir(version.component)}"); + } } public class JavaVersionProps { - [JsonRequired] public Artifact manifest { get; set; } + /// url of JavaDistributiveManifest + [JsonRequired] public Artifact manifest { get; set; } = null!; } -public class JavaVersionManifest +public class JavaDistributiveManifest { - [JsonRequired] public Dictionary manifest { get; set; } + [JsonRequired] public Dictionary files { get; set; } = null!; } public class JavaDistributiveElementProps { - // "directory" / "file" + /// "directory" / "file" [JsonRequired] public string type { get; set; } = ""; public bool? executable { get; set; } public JavaCompressedArtifact? downloads { get; set; } @@ -26,5 +80,5 @@ public class JavaDistributiveElementProps public class JavaCompressedArtifact { public Artifact? lzma { get; set; } - public Artifact raw { get; set; } = null!; + [JsonRequired] public Artifact raw { get; set; } = null!; } \ No newline at end of file diff --git a/Млаумчерб.Клиент/классы/Libraries.cs b/Млаумчерб.Клиент/классы/Libraries.cs index e2effdd..f7e23ae 100644 --- a/Млаумчерб.Клиент/классы/Libraries.cs +++ b/Млаумчерб.Клиент/классы/Libraries.cs @@ -1,4 +1,5 @@ -using DTLib.Extensions; +using System.Runtime.InteropServices; +using DTLib.Extensions; namespace Млаумчерб.Клиент.классы; @@ -35,6 +36,23 @@ public class Libraries if(nativesKey is null) throw new Exception($"nativesKey for '{l.name}' is null"); + // example: "natives-windows-${arch}" + if (nativesKey.Contains('$')) + { + var span = nativesKey.AsSpan(); + nativesKey = span.After("${").Before('}') switch + { + "arch" => RuntimeInformation.OSArchitecture switch + { + Architecture.X64 => span.Before("${").ToString() + "64", + Architecture.X86 => span.Before("${").ToString() + "32", + _ => throw new PlatformNotSupportedException( + $"Unsupported architecture: {RuntimeInformation.OSArchitecture}") + }, + _ => throw new Exception($"unknown placeholder in {nativesKey}") + }; + } + Artifact artifact = null!; if(l.downloads.classifiers != null && !l.downloads.classifiers.TryGetValue(nativesKey, out artifact!)) throw new Exception($"can't find artifact for '{l.name}' with nativesKey '{nativesKey}'"); diff --git a/Млаумчерб.Клиент/классы/Буржуазия.cs b/Млаумчерб.Клиент/классы/Буржуазия.cs index cf10e79..6bd2388 100644 --- a/Млаумчерб.Клиент/классы/Буржуазия.cs +++ b/Млаумчерб.Клиент/классы/Буржуазия.cs @@ -17,7 +17,7 @@ public static class Буржуазия && os.arch switch { null => true, - "x86" => RuntimeInformation.OSArchitecture == Architecture.X86, + "i386" or "x86" => RuntimeInformation.OSArchitecture == Architecture.X86, "x64" => RuntimeInformation.OSArchitecture == Architecture.X64, "arm64" => RuntimeInformation.OSArchitecture == Architecture.Arm64, _ => false diff --git a/Млаумчерб.Клиент/классы/Пути.cs b/Млаумчерб.Клиент/классы/Пути.cs index 4052b69..1c19999 100644 --- a/Млаумчерб.Клиент/классы/Пути.cs +++ b/Млаумчерб.Клиент/классы/Пути.cs @@ -1,4 +1,4 @@ -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; namespace Млаумчерб.Клиент.классы; @@ -7,14 +7,14 @@ public static class Пути public static IOPath GetAssetIndexFilePath(string id) => Path.Concat(Приложение.Настройки.путь_к_кубачу, $"assets/indexes/{id}.json"); - public static IOPath GetVersionDescriptorDir() => - Path.Concat(Приложение.Настройки.путь_к_кубачу, "version_descriptors"); + public static IOPath GetVersionDescriptorsDir() => + Path.Concat(Приложение.Настройки.путь_к_кубачу, "versions"); public static string GetVersionDescriptorName(IOPath path) => path.LastName().RemoveExtension().ToString(); public static IOPath GetVersionDescriptorPath(string name) => - Path.Concat(GetVersionDescriptorDir(), Path.ReplaceRestrictedChars(name) + ".json"); + Path.Concat(GetVersionDescriptorsDir(), Path.ReplaceRestrictedChars(name) + ".json"); public static IOPath GetVersionDir(string id) => Path.Concat(Приложение.Настройки.путь_к_кубачу, "versions", id); diff --git a/Млаумчерб.Клиент/сеть/NetworkProgressReporter.cs b/Млаумчерб.Клиент/сеть/NetworkProgressReporter.cs index cfe29a8..e8a1f39 100644 --- a/Млаумчерб.Клиент/сеть/NetworkProgressReporter.cs +++ b/Млаумчерб.Клиент/сеть/NetworkProgressReporter.cs @@ -1,4 +1,4 @@ -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; using Timer = DTLib.Timer; namespace Млаумчерб.Клиент.сеть; @@ -28,10 +28,9 @@ public class NetworkProgressReporter : IDisposable } // atomic add - public void AddBytesCount(ArraySegment chunk) + public void AddBytesCount(byte[] buffer, int offset, int count) { - long chunkSize = chunk.Count; - Interlocked.Add(ref _curSize, chunkSize); + Interlocked.Add(ref _curSize, count); } public void Start() diff --git a/Млаумчерб.Клиент/сеть/NetworkTask.cs b/Млаумчерб.Клиент/сеть/NetworkTask.cs index c2e724c..d3dca72 100644 --- a/Млаумчерб.Клиент/сеть/NetworkTask.cs +++ b/Млаумчерб.Клиент/сеть/NetworkTask.cs @@ -7,7 +7,7 @@ public class NetworkTask : IDisposable public enum Status { Initialized, - Running, + Started, Completed, Cancelled, Failed @@ -15,6 +15,7 @@ public class NetworkTask : IDisposable public Status DownloadStatus { get; private set; } = Status.Initialized; + public event Action? OnStart; public event Action? OnProgress; public event Action? OnStop; @@ -35,15 +36,20 @@ public class NetworkTask : IDisposable public async Task StartAsync() { - if(DownloadStatus == Status.Running || DownloadStatus == Status.Completed) + if(DownloadStatus == Status.Started || DownloadStatus == Status.Completed) return; - DownloadStatus = Status.Running; + DownloadStatus = Status.Started; try { _progressReporter.Start(); + OnStart?.Invoke(); await _downloadAction(_progressReporter, _cts.Token); DownloadStatus = Status.Completed; } + catch (OperationCanceledException) + { + DownloadStatus = Status.Cancelled; + } catch { DownloadStatus = Status.Failed; diff --git a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/AssetsDownloadTaskFactory.cs b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/AssetsDownloadTaskFactory.cs index 3e5b202..90fbe3f 100644 --- a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/AssetsDownloadTaskFactory.cs +++ b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/AssetsDownloadTaskFactory.cs @@ -1,6 +1,8 @@ -using System.Security.Cryptography; +using System.Net; +using System.Net.Http; +using System.Security.Cryptography; using DTLib.Extensions; -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; using Млаумчерб.Клиент.классы; using static Млаумчерб.Клиент.сеть.Сеть; @@ -37,7 +39,7 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory if(!File.Exists(_indexFilePath)) { Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading asset index to '{_indexFilePath}'"); - await DownloadFileHTTP(_descriptor.assetIndex.url, _indexFilePath); + await DownloadFile(_descriptor.assetIndex.url, _indexFilePath); Приложение.Логгер.LogInfo(nameof(Сеть), "finished downloading asset index"); } @@ -86,7 +88,7 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory public string url; public IOPath filePath; - public AssetDownloadProperties(string key, GameAssetProperties p) + public AssetDownloadProperties(string key, AssetProperties p) { name = key; hash = p.hash; @@ -100,13 +102,35 @@ public class AssetsDownloadTaskFactory : INetworkTaskFactory private async Task Download(NetworkProgressReporter pr, CancellationToken ct) { Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading assets '{_descriptor.assetIndex.id}'"); - ParallelOptions opt = new() { MaxDegreeOfParallelism = ParallelDownloadsN, CancellationToken = ct }; + ParallelOptions opt = new() + { + MaxDegreeOfParallelism = Приложение.Настройки.максимум_параллельных_загрузок, + CancellationToken = ct + }; await Parallel.ForEachAsync(_assetsToDownload, opt, - async (a, _ct) => + async (a, _ct) => + { + bool completed = false; + while(!completed) { Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading asset '{a.name}' {a.hash}"); - await DownloadFileHTTP(a.url, a.filePath, _ct, pr.AddBytesCount); - }); + try + { + await DownloadFile(a.url, a.filePath, _ct, pr.AddBytesCount); + completed = true; + } + catch (HttpRequestException httpException) + { + // wait on rate limit + if(httpException.StatusCode == HttpStatusCode.TooManyRequests) + { + Приложение.Логгер.LogDebug(nameof(Сеть), "rate limit hit"); + await Task.Delay(1000, _ct); + } + else throw; + } + } + }); Приложение.Логгер.LogInfo(nameof(Сеть), $"finished downloading assets '{_descriptor.assetIndex.id}'"); } } \ No newline at end of file diff --git a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/JavaDownloadTaskFactory.cs b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/JavaDownloadTaskFactory.cs index df02e14..96532e0 100644 --- a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/JavaDownloadTaskFactory.cs +++ b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/JavaDownloadTaskFactory.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; -using EasyCompressor; +using DTLib.Extensions; +using Млаумчерб.Клиент.зримое; using Млаумчерб.Клиент.классы; using static Млаумчерб.Клиент.сеть.Сеть; @@ -7,23 +8,27 @@ namespace Млаумчерб.Клиент.сеть.NetworkTaskFactories; public class JavaDownloadTaskFactory : INetworkTaskFactory { - private const string INDEX_URL = + private const string CATALOG_URL = "https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"; private GameVersionDescriptor _descriptor; private IOPath _javaVersionDir; private SHA1 _hasher; - private LZMACompressor _lzma; + private JavaDistributiveManifest? _distributiveManifest; + private List<(IOPath path, JavaDistributiveElementProps props)> _filesToDownload = new(); public JavaDownloadTaskFactory(GameVersionDescriptor descriptor) { _descriptor = descriptor; _javaVersionDir = Пути.GetJavaRuntimeDir(_descriptor.javaVersion.component); _hasher = SHA1.Create(); - _lzma = new LZMACompressor(); } - public Task CreateAsync(bool checkHashes) + public async Task CreateAsync(bool checkHashes) { + var catalog = await DownloadStringAndDeserialize(CATALOG_URL); + var versionProps = catalog.GetVersionProps(_descriptor.javaVersion); + _distributiveManifest = await DownloadStringAndDeserialize(versionProps.manifest.url); + NetworkTask? networkTask = null; if (!CheckFiles(checkHashes)) networkTask = new( @@ -31,26 +36,87 @@ public class JavaDownloadTaskFactory : INetworkTaskFactory GetTotalSize(), Download ); - return Task.FromResult(networkTask); + return networkTask; } private bool CheckFiles(bool checkHashes) { - //TODO: download catalog - //TODO: download manifest for required runtime - //TODO: check whether files from manifest exist and match hashes - throw new NotImplementedException(); + _filesToDownload.Clear(); + foreach (var pair in _distributiveManifest!.files) + { + if (pair.Value.type != "file") + continue; + + if (pair.Value.downloads != null) + { + var artifact = pair.Value.downloads; + IOPath file_path = Path.Concat(_javaVersionDir, pair.Key); + if (!File.Exists(file_path)) + { + _filesToDownload.Add((file_path, pair.Value)); + } + else if(checkHashes) + { + using var fs = File.OpenRead(file_path); + if (_hasher.ComputeHash(fs).HashToString() != artifact.raw.sha1) + { + _filesToDownload.Add((file_path, pair.Value)); + } + } + } + } + + return _filesToDownload.Count == 0; } private long GetTotalSize() { - //TODO: sum up size of all files invalidated by CheckFiles - throw new NotImplementedException(); + long totalSize = 0; + foreach (var file in _filesToDownload) + { + if(file.props.downloads == null) + continue; + totalSize += file.props.downloads.lzma?.size ?? file.props.downloads.raw.size; + } + return totalSize; } - private Task Download(NetworkProgressReporter pr, CancellationToken ct) + private async Task Download(NetworkProgressReporter pr, CancellationToken ct) { - //TODO: download files using lzma decompression - throw new NotImplementedException(); + Приложение.Логгер.LogInfo(nameof(Сеть), "started downloading java runtime " + + $"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'"); + + ParallelOptions opt = new() + { + MaxDegreeOfParallelism = Приложение.Настройки.максимум_параллельных_загрузок, + CancellationToken = ct + }; + await Parallel.ForEachAsync(_filesToDownload, opt, async (f, _ct) => + { + if (f.props.downloads!.lzma != null) + { + Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading lzma-compressed file '{f.path}'"); + await using var pipe = new TransformStream(await GetStream(f.props.downloads.lzma.url, _ct)); + pipe.AddTransform(pr.AddBytesCount); + await using var fs = File.OpenWrite(f.path); + LZMACompressor.Decompress(pipe, fs); + } + else + { + Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading raw file '{f.path}'"); + await DownloadFile(f.props.downloads.raw.url, f.path, _ct, pr.AddBytesCount); + } + + if(!OperatingSystem.IsWindows() && f.props.executable is true) + { + Приложение.Логгер.LogDebug(nameof(Сеть), $"adding execute rights to file '{f.path}'"); + System.IO.File.SetUnixFileMode(f.path.ToString(), UnixFileMode.UserExecute); + } + }); + + Приложение.Логгер.LogInfo(nameof(Сеть), "finished downloading java runtime " + + $"{_descriptor.javaVersion.majorVersion} '{_descriptor.javaVersion.component}'"); } + + } \ No newline at end of file diff --git a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/LibrariesDownloadTaskFactory.cs b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/LibrariesDownloadTaskFactory.cs index b6bb52c..06f6689 100644 --- a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/LibrariesDownloadTaskFactory.cs +++ b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/LibrariesDownloadTaskFactory.cs @@ -1,7 +1,7 @@ using System.IO.Compression; using System.Security.Cryptography; using DTLib.Extensions; -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; using Млаумчерб.Клиент.классы; using static Млаумчерб.Клиент.сеть.Сеть; @@ -73,12 +73,16 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory private async Task Download(NetworkProgressReporter pr, CancellationToken ct) { Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading libraries '{_descriptor.id}'"); - ParallelOptions opt = new() { MaxDegreeOfParallelism = ParallelDownloadsN, CancellationToken = ct }; + + ParallelOptions opt = new() + { + MaxDegreeOfParallelism = Приложение.Настройки.максимум_параллельных_загрузок, + CancellationToken = ct + }; await Parallel.ForEachAsync(_libsToDownload, opt, async (l, _ct) => { - Приложение.Логгер.LogDebug(nameof(Сеть), - $"downloading library '{l.name}' to '{l.jarFilePath}'"); - await DownloadFileHTTP(l.artifact.url, l.jarFilePath, _ct, pr.AddBytesCount); + Приложение.Логгер.LogDebug(nameof(Сеть), $"downloading library '{l.name}' to '{l.jarFilePath}'"); + await DownloadFile(l.artifact.url, l.jarFilePath, _ct, pr.AddBytesCount); if (l is Libraries.NativeLib n) { var zipf = File.OpenRead(n.jarFilePath); @@ -96,6 +100,7 @@ public class LibrariesDownloadTaskFactory : INetworkTaskFactory } } }); + Приложение.Логгер.LogInfo(nameof(Сеть), $"finished downloading libraries '{_descriptor.id}'"); } } \ No newline at end of file diff --git a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/VersionFileDownloadTaskFactory.cs b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/VersionFileDownloadTaskFactory.cs index 2488ed4..d851420 100644 --- a/Млаумчерб.Клиент/сеть/NetworkTaskFactories/VersionFileDownloadTaskFactory.cs +++ b/Млаумчерб.Клиент/сеть/NetworkTaskFactories/VersionFileDownloadTaskFactory.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; using DTLib.Extensions; -using Млаумчерб.Клиент.видимое; +using Млаумчерб.Клиент.зримое; using Млаумчерб.Клиент.классы; using static Млаумчерб.Клиент.сеть.Сеть; @@ -50,7 +50,7 @@ public class VersionFileDownloadTaskFactory : INetworkTaskFactory private async Task Download(NetworkProgressReporter pr, CancellationToken ct) { Приложение.Логгер.LogInfo(nameof(Сеть), $"started downloading version file '{_descriptor.id}'"); - await DownloadFileHTTP(_descriptor.downloads.client.url, _filePath, ct, pr.AddBytesCount); + await DownloadFile(_descriptor.downloads.client.url, _filePath, ct, pr.AddBytesCount); Приложение.Логгер.LogInfo(nameof(Сеть), $"finished downloading version file '{_descriptor.id}'"); } } \ No newline at end of file diff --git a/Млаумчерб.Клиент/сеть/Сеть.cs b/Млаумчерб.Клиент/сеть/Сеть.cs index a6b8dc2..6e2f97a 100644 --- a/Млаумчерб.Клиент/сеть/Сеть.cs +++ b/Млаумчерб.Клиент/сеть/Сеть.cs @@ -1,53 +1,46 @@ -using System.Buffers; -using System.Net.Http; -using Млаумчерб.Клиент.видимое; +using System.Net.Http; +using System.Net.Http.Headers; +using Млаумчерб.Клиент.зримое; using Млаумчерб.Клиент.классы; namespace Млаумчерб.Клиент.сеть; public static class Сеть { - public static int ParallelDownloadsN = 32; - public static HttpClient http = new(); + private static HttpClient _http = new(); - public static async Task DownloadFileHTTP(string url, IOPath outPath, CancellationToken ct = default, - Action>? transformFunc = null) + static Сеть() { - await using var src = await http.GetStreamAsync(url, ct); - await using var dst = File.OpenWrite(outPath); - - await src.CopyTransformAsync(dst, transformFunc, ct).ConfigureAwait(false); + // thanks for Sashok :3 + // https://github.com/new-sashok724/Launcher/blob/23485c3f7de6620d2c6b7b2dd9339c3beb6a0366/Launcher/source/helper/IOHelper.java#L259 + _http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"); } - public static async Task CopyTransformAsync(this Stream src, Stream dst, - Action>? transformFunc = null, CancellationToken ct = default) + public static Task GetString(string url, CancellationToken ct = default) => _http.GetStringAsync(url, ct); + public static Task GetStream(string url, CancellationToken ct = default) => _http.GetStreamAsync(url, ct); + + public static async Task DownloadFile(string url, Stream outStream, CancellationToken ct = default, + params TransformStream.TransformFuncDelegate[] transforms) { - // default dotnet runtime buffer size - int bufferSize = 81920; - byte[] readBuffer = ArrayPool.Shared.Rent(bufferSize); - byte[] writeBuffer = ArrayPool.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(writeBuffer, 0, readCount)); - dst.Write(writeBuffer, 0, readCount); - } - } - catch (OperationCanceledException) - { - } - finally - { - ArrayPool.Shared.Return(readBuffer); - ArrayPool.Shared.Return(writeBuffer); - } + await using var pipe = new TransformStream(await GetStream(url, ct)); + if (transforms.Length > 0) + pipe.AddTransforms(transforms); + await pipe.CopyToAsync(outStream, ct); + } + + public static async Task DownloadFile(string url, IOPath outPath, CancellationToken ct = default, + params TransformStream.TransformFuncDelegate[] transforms) + { + await using var file = File.OpenWrite(outPath); + await DownloadFile(url, file, ct, transforms); + } + + public static async Task DownloadStringAndDeserialize(string url) + { + var text = await _http.GetStringAsync(url); + var result = JsonConvert.DeserializeObject(text) + ?? throw new Exception($"can't deserialize {typeof(T).Name}"); + return result; } private static readonly string[] VERSION_MANIFEST_URLS = @@ -62,10 +55,8 @@ public static class Сеть { try { - var manifestText = await http.GetStringAsync(url); - var catalog = JsonConvert.DeserializeObject(manifestText); - if (catalog != null) - descriptors.AddRange(catalog.versions); + var catalog = await DownloadStringAndDeserialize(url); + descriptors.AddRange(catalog.versions); } catch (Exception ex) { diff --git a/млаумчерб.sln b/млаумчерб.sln index eeea37e..4a36c11 100644 --- a/млаумчерб.sln +++ b/млаумчерб.sln @@ -4,7 +4,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Млаумчерб.Клие EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionFolder", "SolutionFolder", "{A3217C18-CC0D-4CE8-9C48-1BDEC1E1B333}" ProjectSection(SolutionItems) = preProject - nuget.config = nuget.config + .gitignore = .gitignore EndProjectSection EndProject Global