From a8b91f4ee69ef8f722a1cdda6d34aa5873477dc3 Mon Sep 17 00:00:00 2001 From: Philipp Heenemann Date: Wed, 12 Jul 2023 09:19:33 +0200 Subject: [PATCH 1/5] Refactor C# code to imperative, top-level statements style Updated the existing C# code into a more modern, imperative and top-level statements style. This involves shortening the code by removing unnecessary parts like additional brackets and explicit namespace declarations. It's done to improve clarity and readability. --- SPTInstaller/App.axaml.cs | 29 +- SPTInstaller/Behaviors/SpanBehavior.cs | 27 +- SPTInstaller/Controllers/InstallController.cs | 114 ++++--- .../Converters/InvertedProgressConverter.cs | 38 ++- .../CustomControls/DistributedSpacePanel.cs | 108 ++++--- .../CustomControls/PreCheckItem.axaml.cs | 107 ++++--- .../ProgressableTaskItem.axaml.cs | 139 +++++---- .../ProgressableTaskList.axaml.cs | 153 +++++----- .../CustomControls/TaskDetails.axaml.cs | 103 ++++--- SPTInstaller/CustomControls/TitleBar.axaml.cs | 135 ++++----- SPTInstaller/Helpers/DownloadCacheHelper.cs | 262 ++++++++-------- SPTInstaller/Helpers/FileHashHelper.cs | 54 ++-- SPTInstaller/Helpers/FileHelper.cs | 152 +++++----- .../Helpers/HttpClientProgressExtensions.cs | 91 +++--- SPTInstaller/Helpers/PreCheckHelper.cs | 57 ++-- SPTInstaller/Helpers/ProcessHelper.cs | 89 +++--- SPTInstaller/Helpers/ServiceHelper.cs | 210 +++++++------ SPTInstaller/Helpers/ZipHelper.cs | 73 +++-- .../Installer Tasks/CopyClientTask.cs | 44 ++- SPTInstaller/Installer Tasks/DownloadTask.cs | 211 +++++++------ .../Installer Tasks/IntializationTask.cs | 105 ++++--- .../PreChecks/NetCore6PreCheck.cs | 90 +++--- .../PreChecks/NetFramework472PreCheck.cs | 66 ++-- .../Installer Tasks/ReleaseCheckTask.cs | 148 +++++---- .../Installer Tasks/SetupClientTask.cs | 159 +++++----- SPTInstaller/Installer Tasks/TestTask.cs | 54 ++-- SPTInstaller/Interfaces/IPreCheck.cs | 21 +- SPTInstaller/Interfaces/IProgressableTask.cs | 27 +- SPTInstaller/Interfaces/IResult.cs | 17 +- SPTInstaller/Models/DownloadMirror.cs | 13 +- SPTInstaller/Models/InstallerTaskBase.cs | 285 +++++++++--------- SPTInstaller/Models/InternalData.cs | 90 +++--- SPTInstaller/Models/PreCheckBase.cs | 136 ++++----- SPTInstaller/Models/Result.cs | 29 +- SPTInstaller/Program.cs | 85 +++--- SPTInstaller/ViewLocator.cs | 32 +- SPTInstaller/ViewModels/InstallViewModel.cs | 51 ++-- .../ViewModels/MainWindowViewModel.cs | 80 +++-- SPTInstaller/ViewModels/MessageViewModel.cs | 75 +++-- SPTInstaller/ViewModels/ViewModelBase.cs | 99 +++--- SPTInstaller/Views/InstallView.axaml.cs | 14 +- SPTInstaller/Views/MainWindow.axaml.cs | 11 +- SPTInstaller/Views/MessageView.axaml.cs | 14 +- SPTInstaller/Views/PreChecksView.axaml.cs | 13 +- 44 files changed, 1905 insertions(+), 2005 deletions(-) diff --git a/SPTInstaller/App.axaml.cs b/SPTInstaller/App.axaml.cs index cc8165c..ea4dca0 100644 --- a/SPTInstaller/App.axaml.cs +++ b/SPTInstaller/App.axaml.cs @@ -4,26 +4,25 @@ using Avalonia.Markup.Xaml; using SPTInstaller.ViewModels; using SPTInstaller.Views; -namespace SPTInstaller +namespace SPTInstaller; + +public partial class App : Application { - public partial class App : Application + public override void Initialize() { - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } + AvaloniaXamlLoader.Load(this); + } - public override void OnFrameworkInitializationCompleted() + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + desktop.MainWindow = new MainWindow { - desktop.MainWindow = new MainWindow - { - DataContext = new MainWindowViewModel(), - }; - } - - base.OnFrameworkInitializationCompleted(); + DataContext = new MainWindowViewModel(), + }; } + + base.OnFrameworkInitializationCompleted(); } } \ No newline at end of file diff --git a/SPTInstaller/Behaviors/SpanBehavior.cs b/SPTInstaller/Behaviors/SpanBehavior.cs index 6c009fa..db56f13 100644 --- a/SPTInstaller/Behaviors/SpanBehavior.cs +++ b/SPTInstaller/Behaviors/SpanBehavior.cs @@ -1,20 +1,19 @@ using Avalonia; using Avalonia.Interactivity; -namespace SPTInstaller.Behaviors +namespace SPTInstaller.Behaviors; + +public class SpanBehavior : AvaloniaObject { - public class SpanBehavior : AvaloniaObject + public static readonly AttachedProperty SpanProperty = AvaloniaProperty.RegisterAttached("Span"); + + public static void SetSpan(AvaloniaObject element, bool value) { - public static readonly AttachedProperty SpanProperty = AvaloniaProperty.RegisterAttached("Span"); - - public static void SetSpan(AvaloniaObject element, bool value) - { - element.SetValue(SpanProperty, value); - } - - public static bool GetSpan(AvaloniaObject element) - { - return element.GetValue(SpanProperty); - } + element.SetValue(SpanProperty, value); } -} + + public static bool GetSpan(AvaloniaObject element) + { + return element.GetValue(SpanProperty); + } +} \ No newline at end of file diff --git a/SPTInstaller/Controllers/InstallController.cs b/SPTInstaller/Controllers/InstallController.cs index 9669b42..bc7056f 100644 --- a/SPTInstaller/Controllers/InstallController.cs +++ b/SPTInstaller/Controllers/InstallController.cs @@ -2,67 +2,65 @@ using SharpCompress; using SPTInstaller.Interfaces; using SPTInstaller.Models; -using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; -namespace SPTInstaller.Controllers +namespace SPTInstaller.Controllers; + +public class InstallController { - public class InstallController + public event EventHandler TaskChanged = delegate { }; + + private IPreCheck[] _preChecks { get; set; } + private IProgressableTask[] _tasks { get; set; } + + public InstallController(IProgressableTask[] tasks, IPreCheck[] preChecks = null) { - public event EventHandler TaskChanged = delegate { }; - - private IPreCheck[] _preChecks { get; set; } - private IProgressableTask[] _tasks { get; set; } - - public InstallController(IProgressableTask[] tasks, IPreCheck[] preChecks = null) - { - _tasks = tasks; - _preChecks = preChecks; - } - - public async Task RunPreChecks() - { - Log.Information("-<>--<>- Running PreChecks -<>--<>-"); - var requiredResults = new List(); - - _preChecks.ForEach(x => x.IsPending = true); - - foreach (var check in _preChecks) - { - var result = await check.RunCheck(); - - Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}"); - - if (check.IsRequired) - { - requiredResults.Add(result); - } - } - - foreach(var result in requiredResults) - { - if (!result.Succeeded) - return Result.FromError("Some required checks have failed"); - } - - return Result.FromSuccess(); - } - - public async Task RunTasks() - { - Log.Information("-<>--<>- Running Installer Tasks -<>--<>-"); - - foreach (var task in _tasks) - { - TaskChanged?.Invoke(null, task); - - var result = await task.RunAsync(); - - if (!result.Succeeded) return result; - } - - return Result.FromSuccess("Install Complete. Happy Playing!"); - } + _tasks = tasks; + _preChecks = preChecks; } -} + + public async Task RunPreChecks() + { + Log.Information("-<>--<>- Running PreChecks -<>--<>-"); + var requiredResults = new List(); + + _preChecks.ForEach(x => x.IsPending = true); + + foreach (var check in _preChecks) + { + var result = await check.RunCheck(); + + Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}"); + + if (check.IsRequired) + { + requiredResults.Add(result); + } + } + + if (requiredResults.Any(result => !result.Succeeded)) + { + return Result.FromError("Some required checks have failed"); + } + + return Result.FromSuccess(); + } + + public async Task RunTasks() + { + Log.Information("-<>--<>- Running Installer Tasks -<>--<>-"); + + foreach (var task in _tasks) + { + TaskChanged?.Invoke(null, task); + + var result = await task.RunAsync(); + + if (!result.Succeeded) return result; + } + + return Result.FromSuccess("Install Complete. Happy Playing!"); + } +} \ No newline at end of file diff --git a/SPTInstaller/Converters/InvertedProgressConverter.cs b/SPTInstaller/Converters/InvertedProgressConverter.cs index 24d94a7..5ee62d8 100644 --- a/SPTInstaller/Converters/InvertedProgressConverter.cs +++ b/SPTInstaller/Converters/InvertedProgressConverter.cs @@ -1,29 +1,27 @@ using Avalonia.Data.Converters; -using System; using System.Globalization; -namespace SPTInstaller.Converters +namespace SPTInstaller.Converters; + +public class InvertedProgressConverter : IValueConverter { - public class InvertedProgressConverter : IValueConverter + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + if( value is int progress) { - if( value is int progress) - { - return 100 - progress; - } - - return value; + return 100 - progress; } - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - if ( value is int invertedProgress) - { - return 100 - invertedProgress; - } - - return value; - } + return value; } -} + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if ( value is int invertedProgress) + { + return 100 - invertedProgress; + } + + return value; + } +} \ No newline at end of file diff --git a/SPTInstaller/CustomControls/DistributedSpacePanel.cs b/SPTInstaller/CustomControls/DistributedSpacePanel.cs index 783c0a4..e5a847b 100644 --- a/SPTInstaller/CustomControls/DistributedSpacePanel.cs +++ b/SPTInstaller/CustomControls/DistributedSpacePanel.cs @@ -1,77 +1,75 @@ using Avalonia; using Avalonia.Controls; using SPTInstaller.Behaviors; -using System; using System.Linq; -namespace SPTInstaller.CustomControls +namespace SPTInstaller.CustomControls; + +public class DistributedSpacePanel : Panel { - public class DistributedSpacePanel : Panel + protected override Size MeasureOverride(Size availableSize) { - protected override Size MeasureOverride(Size availableSize) + var children = Children; + + for (int i = 0; i < children.Count; i++) { - var children = Children; - - for (int i = 0; i < children.Count; i++) - { - // measure child objects so we can use their desired size in the arrange override - var child = children[i]; - child.Measure(availableSize); - } - - // we want to use all available space - return availableSize; + // measure child objects so we can use their desired size in the arrange override + var child = children[i]; + child.Measure(availableSize); } - protected override Size ArrangeOverride(Size finalSize) + // we want to use all available space + return availableSize; + } + + protected override Size ArrangeOverride(Size finalSize) + { + var children = Children; + Rect rcChild = new Rect(finalSize); + double previousChildSize = 0.0; + + // get child objects that don't want to span the entire control + var nonSpanningChildren = children.Where(x => x.GetValue(SpanBehavior.SpanProperty) == false).ToList(); + + // get the total height off all non-spanning child objects + var totalChildHeight = nonSpanningChildren.Select(x => x.DesiredSize.Height).Sum(); + + // remove the total child height from our controls final size and divide it by the total non-spanning child objects + // except the last one, since it needs no space after it + var spacing = (finalSize.Height - totalChildHeight) / (nonSpanningChildren.Count - 1); + + for (int i = 0; i < children.Count; i++) { - var children = Children; - Rect rcChild = new Rect(finalSize); - double previousChildSize = 0.0; + var child = children[i]; - // get child objects that don't want to span the entire control - var nonSpanningChildren = children.Where(x => x.GetValue(SpanBehavior.SpanProperty) == false).ToList(); + var spanChild = child.GetValue(SpanBehavior.SpanProperty); - // get the total height off all non-spanning child objects - var totalChildHeight = nonSpanningChildren.Select(x => x.DesiredSize.Height).Sum(); - - // remove the total child height from our controls final size and divide it by the total non-spanning child objects - // except the last one, since it needs no space after it - var spacing = (finalSize.Height - totalChildHeight) / (nonSpanningChildren.Count - 1); - - for (int i = 0; i < children.Count; i++) + if (spanChild) { - var child = children[i]; + // move any spanning children to the top of the array to push them behind the other controls (visually) + children.Move(i, 0); - var spanChild = child.GetValue(SpanBehavior.SpanProperty); - - if (spanChild) - { - // move any spanning children to the top of the array to push them behind the other controls (visually) - children.Move(i, 0); - - rcChild = rcChild.WithY(0) - .WithX(0) - .WithHeight(finalSize.Height) - .WithWidth(finalSize.Width); + rcChild = rcChild.WithY(0) + .WithX(0) + .WithHeight(finalSize.Height) + .WithWidth(finalSize.Width); - child.Arrange(rcChild); - continue; - }; - - rcChild = rcChild.WithY(rcChild.Y + previousChildSize); - previousChildSize = child.DesiredSize.Height; - rcChild = rcChild.WithHeight(previousChildSize) - .WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width)); - - previousChildSize += spacing; - child.Arrange(rcChild); - } + continue; + }; - return finalSize; + rcChild = rcChild.WithY(rcChild.Y + previousChildSize); + previousChildSize = child.DesiredSize.Height; + rcChild = rcChild.WithHeight(previousChildSize) + .WithWidth(Math.Max(finalSize.Width, child.DesiredSize.Width)); + + previousChildSize += spacing; + + child.Arrange(rcChild); } + + return finalSize; } -} +} \ No newline at end of file diff --git a/SPTInstaller/CustomControls/PreCheckItem.axaml.cs b/SPTInstaller/CustomControls/PreCheckItem.axaml.cs index 28eb41c..5b7182c 100644 --- a/SPTInstaller/CustomControls/PreCheckItem.axaml.cs +++ b/SPTInstaller/CustomControls/PreCheckItem.axaml.cs @@ -1,62 +1,57 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Shapes; -using Avalonia.Threading; -using ReactiveUI; -using System.Windows.Input; -namespace SPTInstaller.CustomControls +namespace SPTInstaller.CustomControls; + +public partial class PreCheckItem : UserControl { - public partial class PreCheckItem : UserControl + public PreCheckItem() { - public PreCheckItem() - { - InitializeComponent(); - } - - public string PreCheckName - { - get => GetValue(PreCheckNameProperty); - set => SetValue(PreCheckNameProperty, value); - } - - public static readonly StyledProperty PreCheckNameProperty = - AvaloniaProperty.Register(nameof(PreCheckName)); - - public bool IsRunning - { - get => GetValue(IsRunningProperty); - set => SetValue(IsRunningProperty, value); - } - - public static readonly StyledProperty IsRunningProperty = - AvaloniaProperty.Register(nameof(IsRunning)); - - public bool IsPending - { - get => GetValue(IsPendingProperty); - set => SetValue(IsPendingProperty, value); - } - - public static readonly StyledProperty IsPendingProperty = - AvaloniaProperty.Register(nameof(IsPending)); - - public bool Passed - { - get => GetValue(PassedProperty); - set => SetValue(PassedProperty, value); - } - - public static readonly StyledProperty PassedProperty = - AvaloniaProperty.Register(nameof(Passed)); - - public bool IsRequired - { - get => GetValue(IsRequiredProperty); - set => SetValue(IsRequiredProperty, value); - } - - public static readonly StyledProperty IsRequiredProperty = - AvaloniaProperty.Register(nameof(IsRequired)); + InitializeComponent(); } -} + + public string PreCheckName + { + get => GetValue(PreCheckNameProperty); + set => SetValue(PreCheckNameProperty, value); + } + + public static readonly StyledProperty PreCheckNameProperty = + AvaloniaProperty.Register(nameof(PreCheckName)); + + public bool IsRunning + { + get => GetValue(IsRunningProperty); + set => SetValue(IsRunningProperty, value); + } + + public static readonly StyledProperty IsRunningProperty = + AvaloniaProperty.Register(nameof(IsRunning)); + + public bool IsPending + { + get => GetValue(IsPendingProperty); + set => SetValue(IsPendingProperty, value); + } + + public static readonly StyledProperty IsPendingProperty = + AvaloniaProperty.Register(nameof(IsPending)); + + public bool Passed + { + get => GetValue(PassedProperty); + set => SetValue(PassedProperty, value); + } + + public static readonly StyledProperty PassedProperty = + AvaloniaProperty.Register(nameof(Passed)); + + public bool IsRequired + { + get => GetValue(IsRequiredProperty); + set => SetValue(IsRequiredProperty, value); + } + + public static readonly StyledProperty IsRequiredProperty = + AvaloniaProperty.Register(nameof(IsRequired)); +} \ No newline at end of file diff --git a/SPTInstaller/CustomControls/ProgressableTaskItem.axaml.cs b/SPTInstaller/CustomControls/ProgressableTaskItem.axaml.cs index 3c7189e..ceaf72c 100644 --- a/SPTInstaller/CustomControls/ProgressableTaskItem.axaml.cs +++ b/SPTInstaller/CustomControls/ProgressableTaskItem.axaml.cs @@ -2,76 +2,75 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; -namespace SPTInstaller.CustomControls +namespace SPTInstaller.CustomControls; + +public partial class ProgressableTaskItem : UserControl { - public partial class ProgressableTaskItem : UserControl + public ProgressableTaskItem() { - public ProgressableTaskItem() - { - InitializeComponent(); - } - - public string TaskId - { - get => GetValue(TaskIdProperty); - set => SetValue(TaskIdProperty, value); - } - - public static readonly StyledProperty TaskIdProperty = - AvaloniaProperty.Register(nameof(TaskId)); - - public string TaskName - { - get => GetValue(TaskNameProperty); - set => SetValue(TaskNameProperty, value); - } - - public static readonly StyledProperty TaskNameProperty = - AvaloniaProperty.Register(nameof(TaskName)); - - public bool IsCompleted - { - get => GetValue(IsCompletedProperty); - set => SetValue(IsCompletedProperty, value); - } - - public static readonly StyledProperty IsCompletedProperty = - AvaloniaProperty.Register(nameof(IsCompleted)); - - public bool IsRunning - { - get => GetValue(IsRunningProperty); - set => SetValue(IsRunningProperty, value); - } - - public static readonly StyledProperty IsRunningProperty = - AvaloniaProperty.Register(nameof(IsRunning)); - - public IBrush PendingColor - { - get => GetValue(PendingColorProperty); - set => SetValue(PendingColorProperty, value); - } - - public static readonly StyledProperty PendingColorProperty = - AvaloniaProperty.Register(nameof(PendingColor)); - - public IBrush RunningColor - { - get => GetValue(RunningColorProperty); - set => SetValue(RunningColorProperty, value); - } - - public static readonly StyledProperty RunningColorProperty = - AvaloniaProperty.Register(nameof(PendingColor)); - - public IBrush CompletedColor - { - get => GetValue(CompletedColorProperty); - set => SetValue(CompletedColorProperty, value); - } - - public static readonly StyledProperty CompletedColorProperty = - AvaloniaProperty.Register(nameof(PendingColor)); + InitializeComponent(); } -} + + public string TaskId + { + get => GetValue(TaskIdProperty); + set => SetValue(TaskIdProperty, value); + } + + public static readonly StyledProperty TaskIdProperty = + AvaloniaProperty.Register(nameof(TaskId)); + + public string TaskName + { + get => GetValue(TaskNameProperty); + set => SetValue(TaskNameProperty, value); + } + + public static readonly StyledProperty TaskNameProperty = + AvaloniaProperty.Register(nameof(TaskName)); + + public bool IsCompleted + { + get => GetValue(IsCompletedProperty); + set => SetValue(IsCompletedProperty, value); + } + + public static readonly StyledProperty IsCompletedProperty = + AvaloniaProperty.Register(nameof(IsCompleted)); + + public bool IsRunning + { + get => GetValue(IsRunningProperty); + set => SetValue(IsRunningProperty, value); + } + + public static readonly StyledProperty IsRunningProperty = + AvaloniaProperty.Register(nameof(IsRunning)); + + public IBrush PendingColor + { + get => GetValue(PendingColorProperty); + set => SetValue(PendingColorProperty, value); + } + + public static readonly StyledProperty PendingColorProperty = + AvaloniaProperty.Register(nameof(PendingColor)); + + public IBrush RunningColor + { + get => GetValue(RunningColorProperty); + set => SetValue(RunningColorProperty, value); + } + + public static readonly StyledProperty RunningColorProperty = + AvaloniaProperty.Register(nameof(PendingColor)); + + public IBrush CompletedColor + { + get => GetValue(CompletedColorProperty); + set => SetValue(CompletedColorProperty, value); + } + + public static readonly StyledProperty CompletedColorProperty = + AvaloniaProperty.Register(nameof(PendingColor)); +} \ No newline at end of file diff --git a/SPTInstaller/CustomControls/ProgressableTaskList.axaml.cs b/SPTInstaller/CustomControls/ProgressableTaskList.axaml.cs index fbcd063..d725dac 100644 --- a/SPTInstaller/CustomControls/ProgressableTaskList.axaml.cs +++ b/SPTInstaller/CustomControls/ProgressableTaskList.axaml.cs @@ -2,96 +2,93 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; using Avalonia.Threading; -using DynamicData; using DynamicData.Binding; using SPTInstaller.Models; -using System; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; -namespace SPTInstaller.CustomControls +namespace SPTInstaller.CustomControls; + +public partial class ProgressableTaskList : UserControl { - public partial class ProgressableTaskList : UserControl + public ProgressableTaskList() { - public ProgressableTaskList() + InitializeComponent(); + + this.AttachedToVisualTree += ProgressableTaskList_AttachedToVisualTree; + } + + private int _taskProgress; + public int TaskProgress + { + get => _taskProgress; + set => SetAndRaise(ProgressableTaskList.TaskProgressProperty, ref _taskProgress, value); + } + + public static readonly DirectProperty TaskProgressProperty = + AvaloniaProperty.RegisterDirect(nameof(TaskProgress), o => o.TaskProgress); + + public ObservableCollection Tasks + { + get => GetValue(TasksProperty); + set => SetValue(TasksProperty, value); + } + + public static readonly StyledProperty> TasksProperty = + AvaloniaProperty.Register>(nameof(Tasks)); + + public IBrush PendingColor + { + get => GetValue(PendingColorProperty); + set => SetValue(PendingColorProperty, value); + } + + public static readonly StyledProperty PendingColorProperty = + AvaloniaProperty.Register(nameof(PendingColor)); + + public IBrush RunningColor + { + get => GetValue(RunningColorProperty); + set => SetValue(RunningColorProperty, value); + } + + public static readonly StyledProperty RunningColorProperty = + AvaloniaProperty.Register(nameof(PendingColor)); + + public IBrush CompletedColor + { + get => GetValue(CompletedColorProperty); + set => SetValue(CompletedColorProperty, value); + } + + public static readonly StyledProperty CompletedColorProperty = + AvaloniaProperty.Register(nameof(PendingColor)); + + private void UpdateTaskProgress() + { + Dispatcher.UIThread.InvokeAsync(async () => { - InitializeComponent(); + var completedTasks = Tasks.Where(x => x.IsCompleted == true).Count(); - this.AttachedToVisualTree += ProgressableTaskList_AttachedToVisualTree; - } + var progress = (int)Math.Floor((double)completedTasks / (Tasks.Count - 1) * 100); - private int _taskProgress; - public int TaskProgress - { - get => _taskProgress; - set => SetAndRaise(ProgressableTaskList.TaskProgressProperty, ref _taskProgress, value); - } - - public static readonly DirectProperty TaskProgressProperty = - AvaloniaProperty.RegisterDirect(nameof(TaskProgress), o => o.TaskProgress); - - public ObservableCollection Tasks - { - get => GetValue(TasksProperty); - set => SetValue(TasksProperty, value); - } - - public static readonly StyledProperty> TasksProperty = - AvaloniaProperty.Register>(nameof(Tasks)); - - public IBrush PendingColor - { - get => GetValue(PendingColorProperty); - set => SetValue(PendingColorProperty, value); - } - - public static readonly StyledProperty PendingColorProperty = - AvaloniaProperty.Register(nameof(PendingColor)); - - public IBrush RunningColor - { - get => GetValue(RunningColorProperty); - set => SetValue(RunningColorProperty, value); - } - - public static readonly StyledProperty RunningColorProperty = - AvaloniaProperty.Register(nameof(PendingColor)); - - public IBrush CompletedColor - { - get => GetValue(CompletedColorProperty); - set => SetValue(CompletedColorProperty, value); - } - - public static readonly StyledProperty CompletedColorProperty = - AvaloniaProperty.Register(nameof(PendingColor)); - - private void UpdateTaskProgress() - { - Dispatcher.UIThread.InvokeAsync(async () => + for(; TaskProgress < progress;) { - var completedTasks = Tasks.Where(x => x.IsCompleted == true).Count(); - - var progress = (int)Math.Floor((double)completedTasks / (Tasks.Count - 1) * 100); - - for(; TaskProgress < progress;) - { - TaskProgress += 1; - await Task.Delay(1); - } - }); - } - - private void ProgressableTaskList_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) - { - if (Tasks == null) return; - - foreach (var task in Tasks) - { - task.WhenPropertyChanged(x => x.IsCompleted) - .Subscribe(x => UpdateTaskProgress()); + TaskProgress += 1; + await Task.Delay(1); } + }); + } + + private void ProgressableTaskList_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) + { + if (Tasks == null) return; + + foreach (var task in Tasks) + { + task.WhenPropertyChanged(x => x.IsCompleted) + .Subscribe(x => UpdateTaskProgress()); } } -} +} \ No newline at end of file diff --git a/SPTInstaller/CustomControls/TaskDetails.axaml.cs b/SPTInstaller/CustomControls/TaskDetails.axaml.cs index f1be930..31132c4 100644 --- a/SPTInstaller/CustomControls/TaskDetails.axaml.cs +++ b/SPTInstaller/CustomControls/TaskDetails.axaml.cs @@ -1,58 +1,57 @@ using Avalonia; using Avalonia.Controls; -namespace SPTInstaller.CustomControls +namespace SPTInstaller.CustomControls; + +public partial class TaskDetails : UserControl { - public partial class TaskDetails : UserControl + public TaskDetails() { - public TaskDetails() - { - InitializeComponent(); - } - - public string Message - { - get => GetValue(MessageProperty); - set => SetValue(MessageProperty, value); - } - - public static readonly StyledProperty MessageProperty = - AvaloniaProperty.Register(nameof(Message)); - - public string Details - { - get => GetValue(DetailsProperty); - set => SetValue(DetailsProperty, value); - } - - public static readonly StyledProperty DetailsProperty = - AvaloniaProperty.Register(nameof(Details)); - - public int Progress - { - get => GetValue(ProgressProperty); - set => SetValue(ProgressProperty, value); - } - - public static readonly StyledProperty ProgressProperty = - AvaloniaProperty.Register(nameof(Progress)); - - public bool ShowProgress - { - get => GetValue(ShowProgressProperty); - set => SetValue(ShowProgressProperty, value); - } - - public static readonly StyledProperty ShowProgressProperty = - AvaloniaProperty.Register(nameof(ShowProgress)); - - public bool IndeterminateProgress - { - get => GetValue(IndeterminateProgressProperty); - set => SetValue(IndeterminateProgressProperty, value); - } - - public static readonly StyledProperty IndeterminateProgressProperty = - AvaloniaProperty.Register(nameof(IndeterminateProgress)); + InitializeComponent(); } -} + + public string Message + { + get => GetValue(MessageProperty); + set => SetValue(MessageProperty, value); + } + + public static readonly StyledProperty MessageProperty = + AvaloniaProperty.Register(nameof(Message)); + + public string Details + { + get => GetValue(DetailsProperty); + set => SetValue(DetailsProperty, value); + } + + public static readonly StyledProperty DetailsProperty = + AvaloniaProperty.Register(nameof(Details)); + + public int Progress + { + get => GetValue(ProgressProperty); + set => SetValue(ProgressProperty, value); + } + + public static readonly StyledProperty ProgressProperty = + AvaloniaProperty.Register(nameof(Progress)); + + public bool ShowProgress + { + get => GetValue(ShowProgressProperty); + set => SetValue(ShowProgressProperty, value); + } + + public static readonly StyledProperty ShowProgressProperty = + AvaloniaProperty.Register(nameof(ShowProgress)); + + public bool IndeterminateProgress + { + get => GetValue(IndeterminateProgressProperty); + set => SetValue(IndeterminateProgressProperty, value); + } + + public static readonly StyledProperty IndeterminateProgressProperty = + AvaloniaProperty.Register(nameof(IndeterminateProgress)); +} \ No newline at end of file diff --git a/SPTInstaller/CustomControls/TitleBar.axaml.cs b/SPTInstaller/CustomControls/TitleBar.axaml.cs index 5c0c336..fc17d58 100644 --- a/SPTInstaller/CustomControls/TitleBar.axaml.cs +++ b/SPTInstaller/CustomControls/TitleBar.axaml.cs @@ -4,74 +4,73 @@ using Avalonia.Markup.Xaml; using Avalonia.Media; using System.Windows.Input; -namespace SPTInstaller.CustomControls +namespace SPTInstaller.CustomControls; + +public partial class TitleBar : UserControl { - public partial class TitleBar : UserControl + public TitleBar() { - public TitleBar() - { - InitializeComponent(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - - public static readonly StyledProperty TitleProperty = - AvaloniaProperty.Register(nameof(Title)); - - public string Title - { - get => GetValue(TitleProperty); - set => SetValue(TitleProperty, value); - } - - public static readonly StyledProperty ButtonForegroundProperty = - AvaloniaProperty.Register(nameof(ButtonForeground)); - - public IBrush ButtonForeground - { - get => GetValue(ButtonForegroundProperty); - set => SetValue(ButtonForegroundProperty, value); - } - - public static new readonly StyledProperty ForegroundProperty = - AvaloniaProperty.Register(nameof(Foreground)); - - public new IBrush Foreground - { - get => GetValue(ForegroundProperty); - set => SetValue(ForegroundProperty, value); - } - - public static new readonly StyledProperty BackgroundProperty = - AvaloniaProperty.Register(nameof(Background)); - - public new IBrush Background - { - get => GetValue(BackgroundProperty); - set => SetValue(BackgroundProperty, value); - } - - //Close Button Command (X Button) Property - public static readonly StyledProperty XButtonCommandProperty = - AvaloniaProperty.Register(nameof(XButtonCommand)); - - public ICommand XButtonCommand - { - get => GetValue(XButtonCommandProperty); - set => SetValue(XButtonCommandProperty, value); - } - - //Minimize Button Command (- Button) Property - public static readonly StyledProperty MinButtonCommandProperty = - AvaloniaProperty.Register(nameof(MinButtonCommand)); - - public ICommand MinButtonCommand - { - get => GetValue(MinButtonCommandProperty); - set => SetValue(MinButtonCommandProperty, value); - } + InitializeComponent(); } -} + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public static readonly StyledProperty TitleProperty = + AvaloniaProperty.Register(nameof(Title)); + + public string Title + { + get => GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + public static readonly StyledProperty ButtonForegroundProperty = + AvaloniaProperty.Register(nameof(ButtonForeground)); + + public IBrush ButtonForeground + { + get => GetValue(ButtonForegroundProperty); + set => SetValue(ButtonForegroundProperty, value); + } + + public static new readonly StyledProperty ForegroundProperty = + AvaloniaProperty.Register(nameof(Foreground)); + + public new IBrush Foreground + { + get => GetValue(ForegroundProperty); + set => SetValue(ForegroundProperty, value); + } + + public static new readonly StyledProperty BackgroundProperty = + AvaloniaProperty.Register(nameof(Background)); + + public new IBrush Background + { + get => GetValue(BackgroundProperty); + set => SetValue(BackgroundProperty, value); + } + + //Close Button Command (X Button) Property + public static readonly StyledProperty XButtonCommandProperty = + AvaloniaProperty.Register(nameof(XButtonCommand)); + + public ICommand XButtonCommand + { + get => GetValue(XButtonCommandProperty); + set => SetValue(XButtonCommandProperty, value); + } + + //Minimize Button Command (- Button) Property + public static readonly StyledProperty MinButtonCommandProperty = + AvaloniaProperty.Register(nameof(MinButtonCommand)); + + public ICommand MinButtonCommand + { + get => GetValue(MinButtonCommandProperty); + set => SetValue(MinButtonCommandProperty, value); + } +} \ No newline at end of file diff --git a/SPTInstaller/Helpers/DownloadCacheHelper.cs b/SPTInstaller/Helpers/DownloadCacheHelper.cs index 4831e98..30cb479 100644 --- a/SPTInstaller/Helpers/DownloadCacheHelper.cs +++ b/SPTInstaller/Helpers/DownloadCacheHelper.cs @@ -1,146 +1,142 @@ -using HttpClientProgress; +using System.Net.Http; +using System.Threading.Tasks; using Serilog; using SPTInstaller.Models; -using System; -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; -namespace SPTInstaller.Aki.Helper +namespace SPTInstaller.Helpers; + +public static class DownloadCacheHelper { - public static class DownloadCacheHelper + private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromHours(1) }; + + private static string _cachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache"); + + private static bool CheckCache(FileInfo cacheFile, string expectedHash = null) { - private static HttpClient _httpClient = new HttpClient() { Timeout = TimeSpan.FromHours(1) }; - - private static string _cachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache"); - - private static bool CheckCache(FileInfo cacheFile, string expectedHash = null) + try { - try + cacheFile.Refresh(); + Directory.CreateDirectory(_cachePath); + + if (cacheFile.Exists) { + if (expectedHash != null && FileHashHelper.CheckHash(cacheFile, expectedHash)) + { + Log.Information($"Using cached file: {cacheFile.Name} - Hash: {expectedHash}"); + return true; + } + + cacheFile.Delete(); cacheFile.Refresh(); - Directory.CreateDirectory(_cachePath); - - if (cacheFile.Exists) - { - if (expectedHash != null && FileHashHelper.CheckHash(cacheFile, expectedHash)) - { - Log.Information($"Using cached file: {cacheFile.Name} - Hash: {expectedHash}"); - return true; - } - - cacheFile.Delete(); - cacheFile.Refresh(); - } - - return false; - } - catch - { - return false; } + + return false; } - - private static async Task DownloadFile(FileInfo outputFile, string targetLink, IProgress progress, string expectedHash = null) + catch { - try - { - // Use the provided extension method - using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None)) - await _httpClient.DownloadDataAsync(targetLink, file, progress); - - outputFile.Refresh(); - - if (!outputFile.Exists) - { - return Result.FromError($"Failed to download {outputFile.Name}"); - } - - if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash)) - { - return Result.FromError("Hash mismatch"); - } - - return Result.FromSuccess(); - } - catch (Exception ex) - { - return Result.FromError(ex.Message); - } - } - - private static async Task ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null) - { - try - { - if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess(); - - using var patcherFileStream = cacheFile.Open(FileMode.Create); - { - await downloadStream.CopyToAsync(patcherFileStream); - } - - patcherFileStream.Close(); - - if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash)) - { - return Result.FromError("Hash mismatch"); - } - - return Result.FromSuccess(); - } - catch(Exception ex) - { - return Result.FromError(ex.Message); - } - } - - private static async Task ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress progress, string expectedHash = null) - { - try - { - if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess(); - - return await DownloadFile(cacheFile, targetLink, progress, expectedHash); - } - catch(Exception ex) - { - return Result.FromError(ex.Message); - } - } - - public static async Task GetOrDownloadFileAsync(string fileName, string targetLink, IProgress progress, string expectedHash = null) - { - FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName)); - - try - { - var result = await ProcessInboundFileAsync(cacheFile, targetLink, progress, expectedHash); - - return result.Succeeded ? cacheFile : null; - } - catch(Exception ex) - { - Log.Error(ex, $"Error while getting file: {fileName}"); - return null; - } - } - - public static async Task GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null) - { - FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName)); - - try - { - var result = await ProcessInboundStreamAsync(cacheFile, fileDownloadStream, expectedHash); - - return result.Succeeded ? cacheFile : null; - } - catch(Exception ex) - { - Log.Error(ex, $"Error while getting file: {fileName}"); - return null; - } + return false; } } -} + + private static async Task DownloadFile(FileInfo outputFile, string targetLink, IProgress progress, string expectedHash = null) + { + try + { + // Use the provided extension method + using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None)) + await _httpClient.DownloadDataAsync(targetLink, file, progress); + + outputFile.Refresh(); + + if (!outputFile.Exists) + { + return Result.FromError($"Failed to download {outputFile.Name}"); + } + + if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash)) + { + return Result.FromError("Hash mismatch"); + } + + return Result.FromSuccess(); + } + catch (Exception ex) + { + return Result.FromError(ex.Message); + } + } + + private static async Task ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null) + { + try + { + if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess(); + + using var patcherFileStream = cacheFile.Open(FileMode.Create); + { + await downloadStream.CopyToAsync(patcherFileStream); + } + + patcherFileStream.Close(); + + if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash)) + { + return Result.FromError("Hash mismatch"); + } + + return Result.FromSuccess(); + } + catch(Exception ex) + { + return Result.FromError(ex.Message); + } + } + + private static async Task ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress progress, string expectedHash = null) + { + try + { + if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess(); + + return await DownloadFile(cacheFile, targetLink, progress, expectedHash); + } + catch(Exception ex) + { + return Result.FromError(ex.Message); + } + } + + public static async Task GetOrDownloadFileAsync(string fileName, string targetLink, IProgress progress, string expectedHash = null) + { + var cacheFile = new FileInfo(Path.Join(_cachePath, fileName)); + + try + { + var result = await ProcessInboundFileAsync(cacheFile, targetLink, progress, expectedHash); + + return result.Succeeded ? cacheFile : null; + } + catch(Exception ex) + { + Log.Error(ex, $"Error while getting file: {fileName}"); + return null; + } + } + + public static async Task GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null) + { + var cacheFile = new FileInfo(Path.Join(_cachePath, fileName)); + + try + { + var result = await ProcessInboundStreamAsync(cacheFile, fileDownloadStream, expectedHash); + + return result.Succeeded ? cacheFile : null; + } + catch(Exception ex) + { + Log.Error(ex, $"Error while getting file: {fileName}"); + return null; + } + } +} \ No newline at end of file diff --git a/SPTInstaller/Helpers/FileHashHelper.cs b/SPTInstaller/Helpers/FileHashHelper.cs index 2b70e55..315fbe3 100644 --- a/SPTInstaller/Helpers/FileHashHelper.cs +++ b/SPTInstaller/Helpers/FileHashHelper.cs @@ -1,38 +1,34 @@ -using Gitea.Model; -using System; -using System.IO; -using System.Linq; +using System.Linq; using System.Security.Cryptography; using System.Text.RegularExpressions; +using Gitea.Model; -namespace SPTInstaller.Aki.Helper +namespace SPTInstaller.Helpers; + +public static class FileHashHelper { - public static class FileHashHelper + public static string? GetGiteaReleaseHash(Release release) { - public static string GetGiteaReleaseHash(Release release) + var regex = Regex.Match(release.Body, @"Release Hash: (?\S+)"); + + if (regex.Success) { - var regex = Regex.Match(release.Body, @"Release Hash: (?\S+)"); - - if (regex.Success) - { - return regex.Groups["hash"].Value; - } - - return null; + return regex.Groups["hash"].Value; } - public static bool CheckHash(FileInfo file, string expectedHash) - { - using (MD5 md5Service = MD5.Create()) - using (var sourceStream = file.OpenRead()) - { - byte[] sourceHash = md5Service.ComputeHash(sourceStream); - byte[] expectedHashBytes = Convert.FromBase64String(expectedHash); - - bool matched = Enumerable.SequenceEqual(sourceHash, expectedHashBytes); - - return matched; - } - } + return null; } -} + + public static bool CheckHash(FileInfo file, string expectedHash) + { + using var md5Service = MD5.Create(); + using var sourceStream = file.OpenRead(); + + var sourceHash = md5Service.ComputeHash(sourceStream); + var expectedHashBytes = Convert.FromBase64String(expectedHash); + + var matched = Enumerable.SequenceEqual(sourceHash, expectedHashBytes); + + return matched; + } +} \ No newline at end of file diff --git a/SPTInstaller/Helpers/FileHelper.cs b/SPTInstaller/Helpers/FileHelper.cs index d624668..56de581 100644 --- a/SPTInstaller/Helpers/FileHelper.cs +++ b/SPTInstaller/Helpers/FileHelper.cs @@ -1,90 +1,86 @@ -using ReactiveUI; +using System.Text.RegularExpressions; using Serilog; using SPTInstaller.Models; -using System; -using System.IO; -using System.Text.RegularExpressions; -namespace SPTInstaller.Aki.Helper +namespace SPTInstaller.Helpers; + +public static class FileHelper { - public static class FileHelper + private static Result IterateDirectories(DirectoryInfo sourceDir, DirectoryInfo targetDir) { - private static Result IterateDirectories(DirectoryInfo sourceDir, DirectoryInfo targetDir) + try { - try + foreach (var dir in sourceDir.GetDirectories("*", SearchOption.AllDirectories)) { - foreach (var dir in sourceDir.GetDirectories("*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(dir.FullName.Replace(sourceDir.FullName, targetDir.FullName)); - } - return Result.FromSuccess(); - } - catch (Exception ex) - { - Log.Error(ex, "Error while creating directories"); - return Result.FromError(ex.Message); + Directory.CreateDirectory(dir.FullName.Replace(sourceDir.FullName, targetDir.FullName)); } + return Result.FromSuccess(); } - - private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action updateCallback = null) + catch (Exception ex) { - try - { - int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length; - int processedFiles = 0; - - foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories)) - { - updateCallback?.Invoke(file.Name, (int)Math.Floor(((double)processedFiles / totalFiles) * 100)); - - File.Copy(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName), true); - processedFiles++; - } - - return Result.FromSuccess(); - } - catch (Exception ex) - { - Log.Error(ex, "Error while copying files"); - return Result.FromError(ex.Message); - } - } - - public static string GetRedactedPath(string path) - { - var nameMatched = Regex.Match(path, @".:\\[uU]sers\\(?[^\\]+)"); - - if (nameMatched.Success) - { - var name = nameMatched.Groups["NAME"].Value; - return path.Replace(name, "-REDACTED-"); - } - - return path; - } - - public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, IProgress progress = null) => - CopyDirectoryWithProgress(sourceDir, targetDir, (msg, prog) => progress?.Report(prog)); - - public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action updateCallback = null) - { - try - { - var iterateDirectoriesResult = IterateDirectories(sourceDir, targetDir); - - if(!iterateDirectoriesResult.Succeeded) return iterateDirectoriesResult; - - var iterateFilesResult = IterateFiles(sourceDir, targetDir, updateCallback); - - if (!iterateFilesResult.Succeeded) return iterateDirectoriesResult; - - return Result.FromSuccess(); - } - catch (Exception ex) - { - Log.Error(ex, "Error during directory copy"); - return Result.FromError(ex.Message); - } + Log.Error(ex, "Error while creating directories"); + return Result.FromError(ex.Message); } } -} + + private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action updateCallback = null) + { + try + { + int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length; + int processedFiles = 0; + + foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories)) + { + updateCallback?.Invoke(file.Name, (int)Math.Floor(((double)processedFiles / totalFiles) * 100)); + + File.Copy(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName), true); + processedFiles++; + } + + return Result.FromSuccess(); + } + catch (Exception ex) + { + Log.Error(ex, "Error while copying files"); + return Result.FromError(ex.Message); + } + } + + public static string GetRedactedPath(string path) + { + var nameMatched = Regex.Match(path, @".:\\[uU]sers\\(?[^\\]+)"); + + if (nameMatched.Success) + { + var name = nameMatched.Groups["NAME"].Value; + return path.Replace(name, "-REDACTED-"); + } + + return path; + } + + public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, IProgress progress = null) => + CopyDirectoryWithProgress(sourceDir, targetDir, (msg, prog) => progress?.Report(prog)); + + public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action updateCallback = null) + { + try + { + var iterateDirectoriesResult = IterateDirectories(sourceDir, targetDir); + + if(!iterateDirectoriesResult.Succeeded) return iterateDirectoriesResult; + + var iterateFilesResult = IterateFiles(sourceDir, targetDir, updateCallback); + + if (!iterateFilesResult.Succeeded) return iterateDirectoriesResult; + + return Result.FromSuccess(); + } + catch (Exception ex) + { + Log.Error(ex, "Error during directory copy"); + return Result.FromError(ex.Message); + } + } +} \ No newline at end of file diff --git a/SPTInstaller/Helpers/HttpClientProgressExtensions.cs b/SPTInstaller/Helpers/HttpClientProgressExtensions.cs index 3568717..d77154d 100644 --- a/SPTInstaller/Helpers/HttpClientProgressExtensions.cs +++ b/SPTInstaller/Helpers/HttpClientProgressExtensions.cs @@ -1,57 +1,54 @@ -using System; -using System.IO; -using System.Net.Http; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace HttpClientProgress -{ - public static class HttpClientProgressExtensions - { - public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress progress = null, CancellationToken cancellationToken = default(CancellationToken)) - { - using (var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead)) - { - var contentLength = response.Content.Headers.ContentLength; - using (var download = await response.Content.ReadAsStreamAsync()) - { - // no progress... no contentLength... very sad - if (progress is null || !contentLength.HasValue) - { - await download.CopyToAsync(destination); - return; - } - // Such progress and contentLength much reporting Wow! - var progressWrapper = new Progress(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value))); - await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken); - } - } +namespace SPTInstaller.Helpers; - float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f; +public static class HttpClientProgressExtensions +{ + public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress progress = null, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead)) + { + var contentLength = response.Content.Headers.ContentLength; + using (var download = await response.Content.ReadAsStreamAsync()) + { + // no progress... no contentLength... very sad + if (progress is null || !contentLength.HasValue) + { + await download.CopyToAsync(destination); + return; + } + // Such progress and contentLength much reporting Wow! + var progressWrapper = new Progress(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value))); + await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken); + } } - static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default(CancellationToken)) - { - if (bufferSize < 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize)); - if (source is null) - throw new ArgumentNullException(nameof(source)); - if (!source.CanRead) - throw new InvalidOperationException($"'{nameof(source)}' is not readable."); - if (destination == null) - throw new ArgumentNullException(nameof(destination)); - if (!destination.CanWrite) - throw new InvalidOperationException($"'{nameof(destination)}' is not writable."); + float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f; + } - var buffer = new byte[bufferSize]; - long totalBytesRead = 0; - int bytesRead; - while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) - { - await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); - totalBytesRead += bytesRead; - progress?.Report(totalBytesRead); - } + static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default(CancellationToken)) + { + if (bufferSize < 0) + throw new ArgumentOutOfRangeException(nameof(bufferSize)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + if (!source.CanRead) + throw new InvalidOperationException($"'{nameof(source)}' is not readable."); + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + if (!destination.CanWrite) + throw new InvalidOperationException($"'{nameof(destination)}' is not writable."); + + var buffer = new byte[bufferSize]; + long totalBytesRead = 0; + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + progress?.Report(totalBytesRead); } } } \ No newline at end of file diff --git a/SPTInstaller/Helpers/PreCheckHelper.cs b/SPTInstaller/Helpers/PreCheckHelper.cs index 4380020..f9f9d3b 100644 --- a/SPTInstaller/Helpers/PreCheckHelper.cs +++ b/SPTInstaller/Helpers/PreCheckHelper.cs @@ -1,40 +1,37 @@ -using Microsoft.Win32; -using SPTInstaller.Models; -using System; -using System.Diagnostics; -using System.IO; +using System.Diagnostics; using System.Runtime.InteropServices; +using Microsoft.Win32; +using SPTInstaller.Models; -namespace SPTInstaller.Aki.Helper +namespace SPTInstaller.Helpers; + +public static class PreCheckHelper { - public static class PreCheckHelper + private const string registryInstall = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov"; + + public static string DetectOriginalGamePath() { - private const string registryInstall = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov"; + // We can't detect the installed path on non-Windows + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return null; - public static string DetectOriginalGamePath() + var uninstallStringValue = Registry.LocalMachine.OpenSubKey(registryInstall, false) + ?.GetValue("UninstallString"); + var info = (uninstallStringValue is string key) ? new FileInfo(key) : null; + + return info?.DirectoryName; + } + + public static Result DetectOriginalGameVersion(string gamePath) + { + try { - // We can't detect the installed path on non-Windows - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return null; - - var uninstallStringValue = Registry.LocalMachine.OpenSubKey(registryInstall, false) - ?.GetValue("UninstallString"); - var info = (uninstallStringValue is string key) ? new FileInfo(key) : null; - - return info?.DirectoryName; + string version = FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2]; + return Result.FromSuccess(version); } - - public static Result DetectOriginalGameVersion(string gamePath) + catch (Exception ex) { - try - { - string version = FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2]; - return Result.FromSuccess(version); - } - catch (Exception ex) - { - return Result.FromError($"File not found: {ex.Message}"); - } + return Result.FromError($"File not found: {ex.Message}"); } } -} +} \ No newline at end of file diff --git a/SPTInstaller/Helpers/ProcessHelper.cs b/SPTInstaller/Helpers/ProcessHelper.cs index a2c6e9a..63be9dc 100644 --- a/SPTInstaller/Helpers/ProcessHelper.cs +++ b/SPTInstaller/Helpers/ProcessHelper.cs @@ -1,61 +1,60 @@ -using SPTInstaller.Models; -using System.Diagnostics; -using System.IO; +using System.Diagnostics; +using SPTInstaller.Models; -namespace SPTInstaller.Aki.Helper +namespace SPTInstaller.Helpers; + +public enum PatcherExitCode { - public enum PatcherExitCode - { - ProgramClosed = 0, - Success = 10, - EftExeNotFound = 11, - NoPatchFolder = 12, - MissingFile = 13, - MissingDir = 14, - PatchFailed = 15 - } + ProgramClosed = 0, + Success = 10, + EftExeNotFound = 11, + NoPatchFolder = 12, + MissingFile = 13, + MissingDir = 14, + PatchFailed = 15 +} - public static class ProcessHelper +public static class ProcessHelper +{ + public static Result PatchClientFiles(FileInfo executable, DirectoryInfo workingDir) { - public static Result PatchClientFiles(FileInfo executable, DirectoryInfo workingDir) + if (!executable.Exists || !workingDir.Exists) { - if (!executable.Exists || !workingDir.Exists) - { - return Result.FromError($"Could not find executable ({executable.Name}) or working directory ({workingDir.Name})"); - } + return Result.FromError( + $"Could not find executable ({executable.Name}) or working directory ({workingDir.Name})"); + } - var process = new Process(); - process.StartInfo.FileName = executable.FullName; - process.StartInfo.WorkingDirectory = workingDir.FullName; - process.EnableRaisingEvents = true; - process.StartInfo.Arguments = "autoclose"; - process.Start(); + var process = new Process(); + process.StartInfo.FileName = executable.FullName; + process.StartInfo.WorkingDirectory = workingDir.FullName; + process.EnableRaisingEvents = true; + process.StartInfo.Arguments = "autoclose"; + process.Start(); - process.WaitForExit(); + process.WaitForExit(); - switch ((PatcherExitCode)process.ExitCode) - { - case PatcherExitCode.Success: - return Result.FromSuccess("Patcher Finished Successfully, extracting Aki"); + switch ((PatcherExitCode)process.ExitCode) + { + case PatcherExitCode.Success: + return Result.FromSuccess("Patcher Finished Successfully, extracting Aki"); - case PatcherExitCode.ProgramClosed: - return Result.FromError("Patcher was closed before completing!"); + case PatcherExitCode.ProgramClosed: + return Result.FromError("Patcher was closed before completing!"); - case PatcherExitCode.EftExeNotFound: - return Result.FromError("EscapeFromTarkov.exe is missing from the install Path"); + case PatcherExitCode.EftExeNotFound: + return Result.FromError("EscapeFromTarkov.exe is missing from the install Path"); - case PatcherExitCode.NoPatchFolder: - return Result.FromError("Patchers Folder called 'Aki_Patches' is missing"); + case PatcherExitCode.NoPatchFolder: + return Result.FromError("Patchers Folder called 'Aki_Patches' is missing"); - case PatcherExitCode.MissingFile: - return Result.FromError("EFT files was missing a Vital file to continue"); + case PatcherExitCode.MissingFile: + return Result.FromError("EFT files was missing a Vital file to continue"); - case PatcherExitCode.PatchFailed: - return Result.FromError("A patch failed to apply"); + case PatcherExitCode.PatchFailed: + return Result.FromError("A patch failed to apply"); - default: - return Result.FromError("an unknown error occurred in the patcher"); - } + default: + return Result.FromError("an unknown error occurred in the patcher"); } } -} +} \ No newline at end of file diff --git a/SPTInstaller/Helpers/ServiceHelper.cs b/SPTInstaller/Helpers/ServiceHelper.cs index e17b8ed..021eb26 100644 --- a/SPTInstaller/Helpers/ServiceHelper.cs +++ b/SPTInstaller/Helpers/ServiceHelper.cs @@ -1,129 +1,127 @@ using Serilog; using Splat; -using System; using System.Collections.Generic; using System.Linq; -namespace SPTInstaller.Helpers +namespace SPTInstaller.Helpers; + +/// +/// A helper class to handle simple service registration to Splat with constructor parameter injection +/// +/// Splat only recognizes the registered types and doesn't account for interfaces :( +internal static class ServiceHelper { - /// - /// A helper class to handle simple service registration to Splat with constructor parameter injection - /// - /// Splat only recognizes the registered types and doesn't account for interfaces :( - internal static class ServiceHelper + private static bool TryRegisterInstance(object[] parameters = null) { - private static bool TryRegisterInstance(object[] parameters = null) + var instance = Activator.CreateInstance(typeof(T2), parameters); + + if (instance != null) { - var instance = Activator.CreateInstance(typeof(T2), parameters); - - if (instance != null) - { - Locator.CurrentMutable.RegisterConstant((T)instance); - return true; - } - - return false; + Locator.CurrentMutable.RegisterConstant((T)instance); + return true; } - /// - /// Register a class as a service - /// - /// class to register - public static void Register() where T : class => Register(); + return false; + } - /// - /// Register a class as a service by another type - /// - /// type to register as - /// class to register - public static void Register() where T : class + /// + /// Register a class as a service + /// + /// class to register + public static void Register() where T : class => Register(); + + /// + /// Register a class as a service by another type + /// + /// type to register as + /// class to register + public static void Register() where T : class + { + var constructors = typeof(T2).GetConstructors(); + + foreach(var constructor in constructors) { - var constructors = typeof(T2).GetConstructors(); + var parmesan = constructor.GetParameters(); - foreach(var constructor in constructors) + if(parmesan.Length == 0) { - var parmesan = constructor.GetParameters(); + if (TryRegisterInstance()) return; - if(parmesan.Length == 0) - { - if (TryRegisterInstance()) return; + continue; + } - continue; - } + List parameters = new List(); - List parameters = new List(); - - for(int i = 0; i < parmesan.Length; i++) - { - var parm = parmesan[i]; + for(int i = 0; i < parmesan.Length; i++) + { + var parm = parmesan[i]; - var parmValue = Get(parm.ParameterType); + var parmValue = Get(parm.ParameterType); - if (parmValue != null) parameters.Add(parmValue); - } - - if (TryRegisterInstance(parameters.ToArray())) return; - } - } - - /// - /// Get a service from splat - /// - /// - /// - /// Thrown if the service isn't found - public static object Get(Type type) - { - var service = Locator.Current.GetService(type); - - if (service == null) - { - var message = $"Could not locate service of type '{type.Name}'"; - Log.Error(message); - throw new InvalidOperationException(message); + if (parmValue != null) parameters.Add(parmValue); } - return service; - } - - /// - /// Get a service from splat - /// - /// - /// - /// Thrown if the service isn't found - public static T Get() - { - var service = Locator.Current.GetService(); - - if (service == null) - { - var message = $"Could not locate service of type '{nameof(T)}'"; - Log.Error(message); - throw new InvalidOperationException(message); - } - - return service; - } - - /// - /// Get all services of a type - /// - /// - /// - /// thrown if no services are found - public static T[] GetAll() - { - var services = Locator.Current.GetServices().ToArray(); - - if (services == null || services.Count() == 0) - { - var message = $"Could not locate service of type '{nameof(T)}'"; - Log.Error(message); - throw new InvalidOperationException(message); - } - - return services; + if (TryRegisterInstance(parameters.ToArray())) return; } } -} + + /// + /// Get a service from splat + /// + /// + /// + /// Thrown if the service isn't found + public static object Get(Type type) + { + var service = Locator.Current.GetService(type); + + if (service == null) + { + var message = $"Could not locate service of type '{type.Name}'"; + Log.Error(message); + throw new InvalidOperationException(message); + } + + return service; + } + + /// + /// Get a service from splat + /// + /// + /// + /// Thrown if the service isn't found + public static T Get() + { + var service = Locator.Current.GetService(); + + if (service == null) + { + var message = $"Could not locate service of type '{nameof(T)}'"; + Log.Error(message); + throw new InvalidOperationException(message); + } + + return service; + } + + /// + /// Get all services of a type + /// + /// + /// + /// thrown if no services are found + public static T[] GetAll() + { + var services = Locator.Current.GetServices().ToArray(); + + if (services == null || services.Count() == 0) + { + var message = $"Could not locate service of type '{nameof(T)}'"; + Log.Error(message); + throw new InvalidOperationException(message); + } + + return services; + } +} \ No newline at end of file diff --git a/SPTInstaller/Helpers/ZipHelper.cs b/SPTInstaller/Helpers/ZipHelper.cs index 4462ae5..5b970f9 100644 --- a/SPTInstaller/Helpers/ZipHelper.cs +++ b/SPTInstaller/Helpers/ZipHelper.cs @@ -1,56 +1,53 @@ -using SharpCompress.Archives; +using System.Linq; +using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; using SPTInstaller.Models; -using System; -using System.IO; -using System.Linq; -namespace SPTInstaller.Aki.Helper +namespace SPTInstaller.Helpers; + +public static class ZipHelper { - public static class ZipHelper + public static Result Decompress(FileInfo ArchivePath, DirectoryInfo OutputFolderPath, IProgress progress = null) { - public static Result Decompress(FileInfo ArchivePath, DirectoryInfo OutputFolderPath, IProgress progress = null) + try { - try + OutputFolderPath.Refresh(); + + if (!OutputFolderPath.Exists) OutputFolderPath.Create(); + + using var archive = ZipArchive.Open(ArchivePath); + var totalEntries = archive.Entries.Where(entry => !entry.IsDirectory); + var processedEntries = 0; + + foreach (var entry in totalEntries) { - OutputFolderPath.Refresh(); - - if (!OutputFolderPath.Exists) OutputFolderPath.Create(); - - using var archive = ZipArchive.Open(ArchivePath); - var totalEntries = archive.Entries.Where(entry => !entry.IsDirectory); - int processedEntries = 0; - - foreach (var entry in totalEntries) + entry.WriteToDirectory(OutputFolderPath.FullName, new ExtractionOptions() { - entry.WriteToDirectory(OutputFolderPath.FullName, new ExtractionOptions() - { - ExtractFullPath = true, - Overwrite = true - }); + ExtractFullPath = true, + Overwrite = true + }); - processedEntries++; + processedEntries++; - if (progress != null) - { - progress.Report(Math.Floor(((double)processedEntries / totalEntries.Count()) * 100)); - } - } - - OutputFolderPath.Refresh(); - - if (!OutputFolderPath.Exists) + if (progress != null) { - return Result.FromError($"Failed to extract files: {ArchivePath.Name}"); + progress.Report(Math.Floor(((double)processedEntries / totalEntries.Count()) * 100)); } - - return Result.FromSuccess(); } - catch (Exception ex) + + OutputFolderPath.Refresh(); + + if (!OutputFolderPath.Exists) { - return Result.FromError(ex.Message); + return Result.FromError($"Failed to extract files: {ArchivePath.Name}"); } + + return Result.FromSuccess(); + } + catch (Exception ex) + { + return Result.FromError(ex.Message); } } -} +} \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/CopyClientTask.cs b/SPTInstaller/Installer Tasks/CopyClientTask.cs index 1783959..646677a 100644 --- a/SPTInstaller/Installer Tasks/CopyClientTask.cs +++ b/SPTInstaller/Installer Tasks/CopyClientTask.cs @@ -1,30 +1,26 @@ -using Serilog; -using SPTInstaller.Aki.Helper; -using SPTInstaller.Interfaces; +using SPTInstaller.Interfaces; using SPTInstaller.Models; -using System; -using System.IO; using System.Threading.Tasks; +using SPTInstaller.Helpers; -namespace SPTInstaller.Installer_Tasks +namespace SPTInstaller.Installer_Tasks; + +public class CopyClientTask : InstallerTaskBase { - public class CopyClientTask : InstallerTaskBase + private InternalData _data; + + public CopyClientTask(InternalData data) : base("Copy Client Files") { - private InternalData _data; - - public CopyClientTask(InternalData data) : base("Copy Client Files") - { - _data = data; - } - - public override async Task TaskOperation() - { - SetStatus("Copying Client Files", "", 0); - - var originalGameDirInfo = new DirectoryInfo(_data.OriginalGamePath); - var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath); - - return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo, (message, progress) => { SetStatus(null, message, progress, null, true); }); - } + _data = data; } -} + + public override async Task TaskOperation() + { + SetStatus("Copying Client Files", "", 0); + + var originalGameDirInfo = new DirectoryInfo(_data.OriginalGamePath); + var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath); + + return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo, (message, progress) => { SetStatus(null, message, progress, null, true); }); + } +} \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/DownloadTask.cs b/SPTInstaller/Installer Tasks/DownloadTask.cs index 8a91e2e..15e651b 100644 --- a/SPTInstaller/Installer Tasks/DownloadTask.cs +++ b/SPTInstaller/Installer Tasks/DownloadTask.cs @@ -1,126 +1,123 @@ using CG.Web.MegaApiClient; using Newtonsoft.Json; -using SPTInstaller.Aki.Helper; using SPTInstaller.Interfaces; using SPTInstaller.Models; -using System; using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; +using SPTInstaller.Helpers; -namespace SPTInstaller.Installer_Tasks +namespace SPTInstaller.Installer_Tasks; + +public class DownloadTask : InstallerTaskBase { - public class DownloadTask : InstallerTaskBase + private InternalData _data; + + public DownloadTask(InternalData data) : base("Download Files") { - private InternalData _data; + _data = data; + } - public DownloadTask(InternalData data) : base("Download Files") + private async Task BuildMirrorList() + { + var progress = new Progress((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d));}); + + var file = await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress); + + if (file == null) { - _data = data; + return Result.FromError("Failed to download mirror list"); } - private async Task BuildMirrorList() + var mirrorsList = JsonConvert.DeserializeObject>(File.ReadAllText(file.FullName)); + + if (mirrorsList is List mirrors) { - var progress = new Progress((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d));}); - - var file = await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress); - - if (file == null) - { - return Result.FromError("Failed to download mirror list"); - } - - var mirrorsList = JsonConvert.DeserializeObject>(File.ReadAllText(file.FullName)); - - if (mirrorsList is List mirrors) - { - _data.PatcherReleaseMirrors = mirrors; - - return Result.FromSuccess(); - } - - return Result.FromError("Failed to deserialize mirrors list"); - } - - private async Task DownloadPatcherFromMirrors(IProgress progress) - { - foreach (var mirror in _data.PatcherReleaseMirrors) - { - SetStatus($"Downloading Patcher", mirror.Link); - - // mega is a little weird since they use encryption, but thankfully there is a great library for their api :) - if (mirror.Link.StartsWith("https://mega")) - { - var megaClient = new MegaApiClient(); - await megaClient.LoginAnonymousAsync(); - - // if mega fails to connect, try the next mirror - if (!megaClient.IsLoggedIn) continue; - - try - { - using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(mirror.Link), progress); - - _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", megaDownloadStream, mirror.Hash); - - if(_data.PatcherZipInfo == null) - { - continue; - } - - return Result.FromSuccess(); - } - catch - { - //most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas. - continue; - } - } - - _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", mirror.Link, progress, mirror.Hash); - - if (_data.PatcherZipInfo != null) - { - return Result.FromSuccess(); - } - } - - return Result.FromError("Failed to download Patcher"); - } - - public override async Task TaskOperation() - { - var progress = new Progress((d) => { SetStatus(null, null, (int)Math.Floor(d)); }); - - if (_data.PatchNeeded) - { - var buildResult = await BuildMirrorList(); - - if (!buildResult.Succeeded) - { - return buildResult; - } - - SetStatus(null, null, 0); - - var patcherDownloadRresult = await DownloadPatcherFromMirrors(progress); - - if (!patcherDownloadRresult.Succeeded) - { - return patcherDownloadRresult; - } - } - - SetStatus("Downloading SPT-AKI", _data.AkiReleaseDownloadLink, 0); - - _data.AkiZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("sptaki.zip", _data.AkiReleaseDownloadLink, progress, _data.AkiReleaseHash); - - if (_data.AkiZipInfo == null) - { - return Result.FromError("Failed to download spt-aki"); - } + _data.PatcherReleaseMirrors = mirrors; return Result.FromSuccess(); } + + return Result.FromError("Failed to deserialize mirrors list"); + } + + private async Task DownloadPatcherFromMirrors(IProgress progress) + { + foreach (var mirror in _data.PatcherReleaseMirrors) + { + SetStatus($"Downloading Patcher", mirror.Link); + + // mega is a little weird since they use encryption, but thankfully there is a great library for their api :) + if (mirror.Link.StartsWith("https://mega")) + { + var megaClient = new MegaApiClient(); + await megaClient.LoginAnonymousAsync(); + + // if mega fails to connect, try the next mirror + if (!megaClient.IsLoggedIn) continue; + + try + { + using var megaDownloadStream = await megaClient.DownloadAsync(new Uri(mirror.Link), progress); + + _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", megaDownloadStream, mirror.Hash); + + if(_data.PatcherZipInfo == null) + { + continue; + } + + return Result.FromSuccess(); + } + catch + { + //most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas. + continue; + } + } + + _data.PatcherZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("patcher.zip", mirror.Link, progress, mirror.Hash); + + if (_data.PatcherZipInfo != null) + { + return Result.FromSuccess(); + } + } + + return Result.FromError("Failed to download Patcher"); + } + + public override async Task TaskOperation() + { + var progress = new Progress((d) => { SetStatus(null, null, (int)Math.Floor(d)); }); + + if (_data.PatchNeeded) + { + var buildResult = await BuildMirrorList(); + + if (!buildResult.Succeeded) + { + return buildResult; + } + + SetStatus(null, null, 0); + + var patcherDownloadRresult = await DownloadPatcherFromMirrors(progress); + + if (!patcherDownloadRresult.Succeeded) + { + return patcherDownloadRresult; + } + } + + SetStatus("Downloading SPT-AKI", _data.AkiReleaseDownloadLink, 0); + + _data.AkiZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("sptaki.zip", _data.AkiReleaseDownloadLink, progress, _data.AkiReleaseHash); + + if (_data.AkiZipInfo == null) + { + return Result.FromError("Failed to download spt-aki"); + } + + return Result.FromSuccess(); } } \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/IntializationTask.cs b/SPTInstaller/Installer Tasks/IntializationTask.cs index 28dae32..a39c238 100644 --- a/SPTInstaller/Installer Tasks/IntializationTask.cs +++ b/SPTInstaller/Installer Tasks/IntializationTask.cs @@ -1,59 +1,58 @@ -using SPTInstaller.Aki.Helper; -using SPTInstaller.Interfaces; +using SPTInstaller.Interfaces; using SPTInstaller.Models; using System.Threading.Tasks; +using SPTInstaller.Helpers; -namespace SPTInstaller.Installer_Tasks +namespace SPTInstaller.Installer_Tasks; + +public class InitializationTask : InstallerTaskBase { - public class InitializationTask : InstallerTaskBase + private InternalData _data; + + public InitializationTask(InternalData data) : base("Startup") { - private InternalData _data; - - public InitializationTask(InternalData data) : base("Startup") - { - _data = data; - } - - public override async Task TaskOperation() - { - SetStatus("Initializing", $"Target Install Path: {FileHelper.GetRedactedPath(_data.TargetInstallPath)}"); - - _data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath(); - - if (_data.OriginalGamePath == null) - { - return Result.FromError("EFT IS NOT INSTALLED!"); - } - - SetStatus(null, $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}"); - - var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath); - - if (!result.Succeeded) - { - return result; - } - - _data.OriginalGameVersion = result.Message; - - SetStatus(null, $"Installed EFT Game Version: {_data.OriginalGameVersion}"); - - if (_data.OriginalGamePath == null) - { - return Result.FromError("Unable to find original EFT directory, please make sure EFT is installed. Please also run EFT once"); - } - - if (_data.OriginalGamePath == _data.TargetInstallPath) - { - return Result.FromError("Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide"); - } - - if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe"))) - { - return Result.FromError("Installer is located in a folder that has existing game files. Please make sure the installer is in a fresh folder as per the guide"); - } - - return Result.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}"); - } + _data = data; } -} + + public override async Task TaskOperation() + { + SetStatus("Initializing", $"Target Install Path: {FileHelper.GetRedactedPath(_data.TargetInstallPath)}"); + + _data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath(); + + if (_data.OriginalGamePath == null) + { + return Result.FromError("EFT IS NOT INSTALLED!"); + } + + SetStatus(null, $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}"); + + var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath); + + if (!result.Succeeded) + { + return result; + } + + _data.OriginalGameVersion = result.Message; + + SetStatus(null, $"Installed EFT Game Version: {_data.OriginalGameVersion}"); + + if (_data.OriginalGamePath == null) + { + return Result.FromError("Unable to find original EFT directory, please make sure EFT is installed. Please also run EFT once"); + } + + if (_data.OriginalGamePath == _data.TargetInstallPath) + { + return Result.FromError("Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide"); + } + + if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe"))) + { + return Result.FromError("Installer is located in a folder that has existing game files. Please make sure the installer is in a fresh folder as per the guide"); + } + + return Result.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}"); + } +} \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/PreChecks/NetCore6PreCheck.cs b/SPTInstaller/Installer Tasks/PreChecks/NetCore6PreCheck.cs index fa6ffdd..eb7c763 100644 --- a/SPTInstaller/Installer Tasks/PreChecks/NetCore6PreCheck.cs +++ b/SPTInstaller/Installer Tasks/PreChecks/NetCore6PreCheck.cs @@ -1,58 +1,56 @@ using SPTInstaller.Models; -using System; using System.Diagnostics; using System.Threading.Tasks; -namespace SPTInstaller.Installer_Tasks.PreChecks +namespace SPTInstaller.Installer_Tasks.PreChecks; + +public class NetCore6PreCheck : PreCheckBase { - public class NetCore6PreCheck : PreCheckBase + public NetCore6PreCheck() : base(".Net Core 6 Desktop Runtime", false) { - public NetCore6PreCheck() : base(".Net Core 6 Desktop Runtime", false) + } + + public override async Task CheckOperation() + { + var minRequiredVersion = new Version("6.0.0"); + string[] output; + + try { + var proc = Process.Start(new ProcessStartInfo() + { + FileName = "dotnet", + Arguments = "--list-runtimes", + RedirectStandardOutput = true, + CreateNoWindow = true + }); + + proc.WaitForExit(); + + output = proc.StandardOutput.ReadToEnd().Split("\r\n"); } - - public override async Task CheckOperation() + catch (Exception ex) { - var minRequiredVersion = new Version("6.0.0"); - string[] output; - - try - { - var proc = Process.Start(new ProcessStartInfo() - { - FileName = "dotnet", - Arguments = "--list-runtimes", - RedirectStandardOutput = true, - CreateNoWindow = true - }); - - proc.WaitForExit(); - - output = proc.StandardOutput.ReadToEnd().Split("\r\n"); - } - catch (Exception ex) - { - // TODO: logging - return false; - } - - foreach (var lineVersion in output) - { - if (lineVersion.StartsWith("Microsoft.WindowsDesktop.App") && lineVersion.Split(" ").Length > 1) - { - string stringVerion = lineVersion.Split(" ")[1]; - - var foundVersion = new Version(stringVerion); - - // not fully sure if we should only check for 6.x.x versions or if higher major versions are ok -waffle - if (foundVersion >= minRequiredVersion) - { - return true; - } - } - } - + // TODO: logging return false; } + + foreach (var lineVersion in output) + { + if (lineVersion.StartsWith("Microsoft.WindowsDesktop.App") && lineVersion.Split(" ").Length > 1) + { + string stringVerion = lineVersion.Split(" ")[1]; + + var foundVersion = new Version(stringVerion); + + // not fully sure if we should only check for 6.x.x versions or if higher major versions are ok -waffle + if (foundVersion >= minRequiredVersion) + { + return true; + } + } + } + + return false; } -} +} \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/PreChecks/NetFramework472PreCheck.cs b/SPTInstaller/Installer Tasks/PreChecks/NetFramework472PreCheck.cs index f190460..55c8160 100644 --- a/SPTInstaller/Installer Tasks/PreChecks/NetFramework472PreCheck.cs +++ b/SPTInstaller/Installer Tasks/PreChecks/NetFramework472PreCheck.cs @@ -1,46 +1,44 @@ using Microsoft.Win32; using SPTInstaller.Models; -using System; using System.Threading.Tasks; -namespace SPTInstaller.Installer_Tasks.PreChecks +namespace SPTInstaller.Installer_Tasks.PreChecks; + +public class NetFramework472PreCheck : PreCheckBase { - public class NetFramework472PreCheck : PreCheckBase + public NetFramework472PreCheck() : base(".Net Framework 4.7.2", false) { - public NetFramework472PreCheck() : base(".Net Framework 4.7.2", false) + } + + public override async Task CheckOperation() + { + try { + var minRequiredVersion = new Version("4.7.2"); + + var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full"); + + if (key == null) + { + return false; + } + + var value = key.GetValue("Version"); + + if (value != null && value is string versionString) + { + var installedVersion = new Version(versionString); + + return installedVersion > minRequiredVersion; + } + + return false; } - - public override async Task CheckOperation() + catch (Exception ex) { - try - { - var minRequiredVersion = new Version("4.7.2"); + // TODO: log exceptions - var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full"); - - if (key == null) - { - return false; - } - - var value = key.GetValue("Version"); - - if (value != null && value is string versionString) - { - var installedVersion = new Version(versionString); - - return installedVersion > minRequiredVersion; - } - - return false; - } - catch (Exception ex) - { - // TODO: log exceptions - - return false; - } + return false; } } -} +} \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/ReleaseCheckTask.cs b/SPTInstaller/Installer Tasks/ReleaseCheckTask.cs index edddeda..e28b14e 100644 --- a/SPTInstaller/Installer Tasks/ReleaseCheckTask.cs +++ b/SPTInstaller/Installer Tasks/ReleaseCheckTask.cs @@ -1,89 +1,87 @@ using Gitea.Api; using Gitea.Client; -using SPTInstaller.Aki.Helper; using SPTInstaller.Interfaces; using SPTInstaller.Models; -using System; using System.Threading.Tasks; +using SPTInstaller.Helpers; -namespace SPTInstaller.Installer_Tasks +namespace SPTInstaller.Installer_Tasks; + +public class ReleaseCheckTask : InstallerTaskBase { - public class ReleaseCheckTask : InstallerTaskBase + private InternalData _data; + + public ReleaseCheckTask(InternalData data) : base("Release Checks") { - private InternalData _data; + _data = data; + } - public ReleaseCheckTask(InternalData data) : base("Release Checks") + public override async Task TaskOperation() + { + try { - _data = data; + Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1"; + + var repo = new RepositoryApi(Configuration.Default); + + SetStatus("Checking SPT Releases", "", null, ProgressStyle.Indeterminate); + + var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases"); + + SetStatus("Checking for Patches", "", null, ProgressStyle.Indeterminate); + + var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches"); + + var latestAkiRelease = akiRepoReleases.FindAll(x => !x.Prerelease)[0]; + var latestAkiVersion = latestAkiRelease.Name.Replace('(', ' ').Replace(')', ' ').Split(' ')[3]; + var comparePatchToAki = patchRepoReleases?.Find(x => x.Name.Contains(_data.OriginalGameVersion) && x.Name.Contains(latestAkiVersion)); + + _data.PatcherMirrorsLink = comparePatchToAki?.Assets[0].BrowserDownloadUrl; + _data.AkiReleaseDownloadLink = latestAkiRelease.Assets[0].BrowserDownloadUrl; + _data.AkiReleaseHash = FileHashHelper.GetGiteaReleaseHash(latestAkiRelease); + + int IntAkiVersion = int.Parse(latestAkiVersion); + int IntGameVersion = int.Parse(_data.OriginalGameVersion); + bool patchNeedCheck = false; + + if (IntGameVersion > IntAkiVersion) + { + patchNeedCheck = true; + } + + if (IntGameVersion < IntAkiVersion) + { + return Result.FromError("Your client is outdated. Please update EFT"); + + } + + if (IntGameVersion == IntAkiVersion) + { + patchNeedCheck = false; + } + + if (comparePatchToAki == null && patchNeedCheck) + { + return Result.FromError("No patcher available for your version"); + } + + _data.PatchNeeded = patchNeedCheck; + + string status = $"Current Release: {latestAkiVersion}"; + + if (_data.PatchNeeded) + { + status += " - Patch Available"; + } + + SetStatus(null, status); + + return Result.FromSuccess(status); } - - public override async Task TaskOperation() + catch (Exception ex) { - try - { - Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1"; - - var repo = new RepositoryApi(Configuration.Default); - - SetStatus("Checking SPT Releases", "", null, ProgressStyle.Indeterminate); - - var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases"); - - SetStatus("Checking for Patches", "", null, ProgressStyle.Indeterminate); - - var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches"); - - var latestAkiRelease = akiRepoReleases.FindAll(x => !x.Prerelease)[0]; - var latestAkiVersion = latestAkiRelease.Name.Replace('(', ' ').Replace(')', ' ').Split(' ')[3]; - var comparePatchToAki = patchRepoReleases?.Find(x => x.Name.Contains(_data.OriginalGameVersion) && x.Name.Contains(latestAkiVersion)); - - _data.PatcherMirrorsLink = comparePatchToAki?.Assets[0].BrowserDownloadUrl; - _data.AkiReleaseDownloadLink = latestAkiRelease.Assets[0].BrowserDownloadUrl; - _data.AkiReleaseHash = FileHashHelper.GetGiteaReleaseHash(latestAkiRelease); - - int IntAkiVersion = int.Parse(latestAkiVersion); - int IntGameVersion = int.Parse(_data.OriginalGameVersion); - bool patchNeedCheck = false; - - if (IntGameVersion > IntAkiVersion) - { - patchNeedCheck = true; - } - - if (IntGameVersion < IntAkiVersion) - { - return Result.FromError("Your client is outdated. Please update EFT"); - - } - - if (IntGameVersion == IntAkiVersion) - { - patchNeedCheck = false; - } - - if (comparePatchToAki == null && patchNeedCheck) - { - return Result.FromError("No patcher available for your version"); - } - - _data.PatchNeeded = patchNeedCheck; - - string status = $"Current Release: {latestAkiVersion}"; - - if (_data.PatchNeeded) - { - status += " - Patch Available"; - } - - SetStatus(null, status); - - return Result.FromSuccess(status); - } - catch (Exception ex) - { - //request failed - return Result.FromError($"Request Failed:\n{ex.Message}"); - } + //request failed + return Result.FromError($"Request Failed:\n{ex.Message}"); } } -} +} \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/SetupClientTask.cs b/SPTInstaller/Installer Tasks/SetupClientTask.cs index e577659..e62e2f1 100644 --- a/SPTInstaller/Installer Tasks/SetupClientTask.cs +++ b/SPTInstaller/Installer Tasks/SetupClientTask.cs @@ -1,88 +1,85 @@ -using SPTInstaller.Aki.Helper; -using SPTInstaller.Interfaces; +using SPTInstaller.Interfaces; using SPTInstaller.Models; -using System; -using System.IO; using System.Linq; using System.Threading.Tasks; +using SPTInstaller.Helpers; -namespace SPTInstaller.Installer_Tasks +namespace SPTInstaller.Installer_Tasks; + +public class SetupClientTask : InstallerTaskBase { - public class SetupClientTask : InstallerTaskBase + private InternalData _data; + + public SetupClientTask(InternalData data) : base("Setup Client") { - private InternalData _data; - - public SetupClientTask(InternalData data) : base("Setup Client") - { - _data = data; - } - - public override async Task TaskOperation() - { - var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath); - - var patcherOutputDir = new DirectoryInfo(Path.Join(_data.TargetInstallPath, "patcher")); - - var patcherEXE = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.exe")); - - var progress = new Progress((d) => { SetStatus(null, null, (int)Math.Floor(d)); }); - - - if (_data.PatchNeeded) - { - // extract patcher files - SetStatus("Extrating Patcher", "", 0); - - var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, progress); - - if (!extractPatcherResult.Succeeded) - { - return extractPatcherResult; - } - - // copy patcher files to install directory - SetStatus("Copying Patcher", "", 0); - - var patcherDirInfo = patcherOutputDir.GetDirectories("Patcher*", SearchOption.TopDirectoryOnly).First(); - - var copyPatcherResult = FileHelper.CopyDirectoryWithProgress(patcherDirInfo, targetInstallDirInfo, progress); - - if (!copyPatcherResult.Succeeded) - { - return copyPatcherResult; - } - - // run patcher - SetStatus("Running Patcher", "", null, ProgressStyle.Indeterminate); - - var patchingResult = ProcessHelper.PatchClientFiles(patcherEXE, targetInstallDirInfo); - - if (!patchingResult.Succeeded) - { - return patchingResult; - } - } - - // extract release files - SetStatus("Extracting Release", "", 0); - - var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, progress); - - if (!extractReleaseResult.Succeeded) - { - return extractReleaseResult; - } - - // cleanup temp files - SetStatus("Cleanup", "almost done :)", null, ProgressStyle.Indeterminate); - - if(_data.PatchNeeded) - { - patcherOutputDir.Delete(true); - patcherEXE.Delete(); - } - - return Result.FromSuccess("SPT is Setup. Happy Playing!"); - } + _data = data; } -} + + public override async Task TaskOperation() + { + var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath); + + var patcherOutputDir = new DirectoryInfo(Path.Join(_data.TargetInstallPath, "patcher")); + + var patcherEXE = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.exe")); + + var progress = new Progress((d) => { SetStatus(null, null, (int)Math.Floor(d)); }); + + + if (_data.PatchNeeded) + { + // extract patcher files + SetStatus("Extrating Patcher", "", 0); + + var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, progress); + + if (!extractPatcherResult.Succeeded) + { + return extractPatcherResult; + } + + // copy patcher files to install directory + SetStatus("Copying Patcher", "", 0); + + var patcherDirInfo = patcherOutputDir.GetDirectories("Patcher*", SearchOption.TopDirectoryOnly).First(); + + var copyPatcherResult = FileHelper.CopyDirectoryWithProgress(patcherDirInfo, targetInstallDirInfo, progress); + + if (!copyPatcherResult.Succeeded) + { + return copyPatcherResult; + } + + // run patcher + SetStatus("Running Patcher", "", null, ProgressStyle.Indeterminate); + + var patchingResult = ProcessHelper.PatchClientFiles(patcherEXE, targetInstallDirInfo); + + if (!patchingResult.Succeeded) + { + return patchingResult; + } + } + + // extract release files + SetStatus("Extracting Release", "", 0); + + var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, progress); + + if (!extractReleaseResult.Succeeded) + { + return extractReleaseResult; + } + + // cleanup temp files + SetStatus("Cleanup", "almost done :)", null, ProgressStyle.Indeterminate); + + if(_data.PatchNeeded) + { + patcherOutputDir.Delete(true); + patcherEXE.Delete(); + } + + return Result.FromSuccess("SPT is Setup. Happy Playing!"); + } +} \ No newline at end of file diff --git a/SPTInstaller/Installer Tasks/TestTask.cs b/SPTInstaller/Installer Tasks/TestTask.cs index 2ce0d1a..537ef50 100644 --- a/SPTInstaller/Installer Tasks/TestTask.cs +++ b/SPTInstaller/Installer Tasks/TestTask.cs @@ -1,35 +1,33 @@ using SPTInstaller.Interfaces; using SPTInstaller.Models; -using System; using System.Threading.Tasks; -namespace SPTInstaller.Installer_Tasks +namespace SPTInstaller.Installer_Tasks; + +internal class TestTask : InstallerTaskBase { - internal class TestTask : InstallerTaskBase + public static TestTask FromRandomName() => new TestTask($"Test Task #{new Random().Next(0, 9999)}"); + + public TestTask(string name) : base(name) { - public static TestTask FromRandomName() => new TestTask($"Test Task #{new Random().Next(0, 9999)}"); - - public TestTask(string name) : base(name) - { - } - - public async override Task TaskOperation() - { - int total = 4; - TimeSpan interval = TimeSpan.FromSeconds(1); - - for(int i = 0; i < total; i++) - { - var count = i + 1; - string progressMessage = $"Running Task: {Name}"; - int progress = (int)Math.Floor((double)count / total * 100); - - SetStatus(progressMessage, $"Details: ({count}/{total})", progress); - - await Task.Delay(interval); - } - - return Result.FromSuccess(); - } } -} + + public async override Task TaskOperation() + { + var total = 4; + var interval = TimeSpan.FromSeconds(1); + + for(var i = 0; i < total; i++) + { + var count = i + 1; + var progressMessage = $"Running Task: {Name}"; + var progress = (int)Math.Floor((double)count / total * 100); + + SetStatus(progressMessage, $"Details: ({count}/{total})", progress); + + await Task.Delay(interval); + } + + return Result.FromSuccess(); + } +} \ No newline at end of file diff --git a/SPTInstaller/Interfaces/IPreCheck.cs b/SPTInstaller/Interfaces/IPreCheck.cs index 0618875..34f20cc 100644 --- a/SPTInstaller/Interfaces/IPreCheck.cs +++ b/SPTInstaller/Interfaces/IPreCheck.cs @@ -1,17 +1,16 @@ using System.Threading.Tasks; -namespace SPTInstaller.Interfaces +namespace SPTInstaller.Interfaces; + +public interface IPreCheck { - public interface IPreCheck - { - public string Id { get; } - public string Name { get; } - public bool IsRequired { get; } + public string Id { get; } + public string Name { get; } + public bool IsRequired { get; } - public bool IsPending { get; set; } + public bool IsPending { get; set; } - public bool Passed { get; } + public bool Passed { get; } - public Task RunCheck(); - } -} + public Task RunCheck(); +} \ No newline at end of file diff --git a/SPTInstaller/Interfaces/IProgressableTask.cs b/SPTInstaller/Interfaces/IProgressableTask.cs index 3eb58b2..5d343bf 100644 --- a/SPTInstaller/Interfaces/IProgressableTask.cs +++ b/SPTInstaller/Interfaces/IProgressableTask.cs @@ -1,24 +1,23 @@ using System.Threading.Tasks; -namespace SPTInstaller.Interfaces +namespace SPTInstaller.Interfaces; + +public interface IProgressableTask { - public interface IProgressableTask - { - public string Id { get; } - public string Name { get; } + public string Id { get; } + public string Name { get; } - public bool IsCompleted { get; } + public bool IsCompleted { get; } - public bool HasErrors { get; } + public bool HasErrors { get; } - public bool IsRunning { get; } + public bool IsRunning { get; } - public string StatusMessage { get; } + public string StatusMessage { get; } - public int Progress { get; } + public int Progress { get; } - public bool ShowProgress { get; } + public bool ShowProgress { get; } - public Task RunAsync(); - } -} + public Task RunAsync(); +} \ No newline at end of file diff --git a/SPTInstaller/Interfaces/IResult.cs b/SPTInstaller/Interfaces/IResult.cs index 43d9a75..46bf9c5 100644 --- a/SPTInstaller/Interfaces/IResult.cs +++ b/SPTInstaller/Interfaces/IResult.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace SPTInstaller.Interfaces; -namespace SPTInstaller.Interfaces +public interface IResult { - public interface IResult - { - public bool Succeeded { get; } - public string Message { get; } - } -} + public bool Succeeded { get; } + public string Message { get; } +} \ No newline at end of file diff --git a/SPTInstaller/Models/DownloadMirror.cs b/SPTInstaller/Models/DownloadMirror.cs index c3dbc2c..40a83fd 100644 --- a/SPTInstaller/Models/DownloadMirror.cs +++ b/SPTInstaller/Models/DownloadMirror.cs @@ -1,8 +1,7 @@ -namespace SPTInstaller.Models +namespace SPTInstaller.Models; + +public class DownloadMirror { - public class DownloadMirror - { - public string Link { get; set; } - public string Hash { get; set; } - } -} + public string Link { get; set; } + public string Hash { get; set; } +} \ No newline at end of file diff --git a/SPTInstaller/Models/InstallerTaskBase.cs b/SPTInstaller/Models/InstallerTaskBase.cs index eb939cb..bafa7f0 100644 --- a/SPTInstaller/Models/InstallerTaskBase.cs +++ b/SPTInstaller/Models/InstallerTaskBase.cs @@ -1,183 +1,178 @@ -using Avalonia.Threading; -using ReactiveUI; +using ReactiveUI; using Serilog; -using Splat; using SPTInstaller.Interfaces; -using System; -using System.Security; using System.Threading.Tasks; -namespace SPTInstaller.Models +namespace SPTInstaller.Models; + +public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask { - public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask + private string _id; + public string Id { - private string _id; - public string Id - { - get => _id; - private set => this.RaiseAndSetIfChanged(ref _id, value); - } + get => _id; + private set => this.RaiseAndSetIfChanged(ref _id, value); + } - private string _name; - public string Name - { - get => _name; - private set => this.RaiseAndSetIfChanged(ref _name, value); - } + private string _name; + public string Name + { + get => _name; + private set => this.RaiseAndSetIfChanged(ref _name, value); + } - private bool _isComleted; - public bool IsCompleted - { - get => _isComleted; - private set => this.RaiseAndSetIfChanged(ref _isComleted, value); - } + private bool _isComleted; + public bool IsCompleted + { + get => _isComleted; + private set => this.RaiseAndSetIfChanged(ref _isComleted, value); + } - private bool _hasErrors; - public bool HasErrors - { - get => _hasErrors; - private set => this.RaiseAndSetIfChanged(ref _hasErrors, value); - } + private bool _hasErrors; + public bool HasErrors + { + get => _hasErrors; + private set => this.RaiseAndSetIfChanged(ref _hasErrors, value); + } - private bool _isRunning; - public bool IsRunning - { - get => _isRunning; - private set => this.RaiseAndSetIfChanged(ref _isRunning, value); - } + private bool _isRunning; + public bool IsRunning + { + get => _isRunning; + private set => this.RaiseAndSetIfChanged(ref _isRunning, value); + } - private int _progress; - public int Progress - { - get => _progress; - private set => this.RaiseAndSetIfChanged(ref _progress, value); - } + private int _progress; + public int Progress + { + get => _progress; + private set => this.RaiseAndSetIfChanged(ref _progress, value); + } - private bool _showProgress; - public bool ShowProgress - { - get => _showProgress; - private set => this.RaiseAndSetIfChanged(ref _showProgress, value); - } + private bool _showProgress; + public bool ShowProgress + { + get => _showProgress; + private set => this.RaiseAndSetIfChanged(ref _showProgress, value); + } - private bool _indeterminateProgress; - public bool IndeterminateProgress - { - get => _indeterminateProgress; - private set => this.RaiseAndSetIfChanged(ref _indeterminateProgress, value); - } + private bool _indeterminateProgress; + public bool IndeterminateProgress + { + get => _indeterminateProgress; + private set => this.RaiseAndSetIfChanged(ref _indeterminateProgress, value); + } - private string _statusMessage; - public string StatusMessage - { - get => _statusMessage; - private set => this.RaiseAndSetIfChanged(ref _statusMessage, value); - } + private string _statusMessage; + public string StatusMessage + { + get => _statusMessage; + private set => this.RaiseAndSetIfChanged(ref _statusMessage, value); + } - private string _statusDetails; - public string StatusDetails - { - get => _statusDetails; - private set => this.RaiseAndSetIfChanged(ref _statusDetails, value); - } + private string _statusDetails; + public string StatusDetails + { + get => _statusDetails; + private set => this.RaiseAndSetIfChanged(ref _statusDetails, value); + } - public enum ProgressStyle - { - Hidden = 0, - Shown, - Indeterminate, - } + public enum ProgressStyle + { + Hidden = 0, + Shown, + Indeterminate, + } - /// - /// Update the status details of the task - /// - /// The main message to display. Not updated if null - /// The details of the task. Not updated if null - /// Progress of the task. Overrides progressStyle if a non-null value is supplied - /// The style of the progress bar - public void SetStatus(string? message, string? details, int? progress = null, ProgressStyle? progressStyle = null, bool noLog = false) + /// + /// Update the status details of the task + /// + /// The main message to display. Not updated if null + /// The details of the task. Not updated if null + /// Progress of the task. Overrides progressStyle if a non-null value is supplied + /// The style of the progress bar + public void SetStatus(string? message, string? details, int? progress = null, ProgressStyle? progressStyle = null, bool noLog = false) + { + if(message != null && message != StatusMessage) { - if(message != null && message != StatusMessage) + if (!noLog && !string.IsNullOrWhiteSpace(message)) { - if (!noLog && !string.IsNullOrWhiteSpace(message)) - { - Log.Information($" <===> {message} <===>"); - } - - StatusMessage = message; + Log.Information($" <===> {message} <===>"); } - if(details != null && details != StatusDetails) - { - if (!noLog && !string.IsNullOrWhiteSpace(details)) - { - Log.Information(details); - } + StatusMessage = message; + } - StatusDetails = details; + if(details != null && details != StatusDetails) + { + if (!noLog && !string.IsNullOrWhiteSpace(details)) + { + Log.Information(details); } - if (progressStyle != null) - { - switch (progressStyle) - { - case ProgressStyle.Hidden: - ShowProgress = false; - IndeterminateProgress = false; - break; - case ProgressStyle.Shown: - ShowProgress = true; - IndeterminateProgress = false; - break; - case ProgressStyle.Indeterminate: - ShowProgress = true; - IndeterminateProgress = true; - break; - } - } + StatusDetails = details; + } - if (progress != null) + if (progressStyle != null) + { + switch (progressStyle) { - ShowProgress = true; - IndeterminateProgress = false; - Progress = progress.Value; + case ProgressStyle.Hidden: + ShowProgress = false; + IndeterminateProgress = false; + break; + case ProgressStyle.Shown: + ShowProgress = true; + IndeterminateProgress = false; + break; + case ProgressStyle.Indeterminate: + ShowProgress = true; + IndeterminateProgress = true; + break; } } - public InstallerTaskBase(string name) + if (progress != null) { - Name = name; - Id = Guid.NewGuid().ToString(); + ShowProgress = true; + IndeterminateProgress = false; + Progress = progress.Value; } + } - /// - /// A method for the install controller to call. Do not use this within your task - /// - /// - public async Task RunAsync() + public InstallerTaskBase(string name) + { + Name = name; + Id = Guid.NewGuid().ToString(); + } + + /// + /// A method for the install controller to call. Do not use this within your task + /// + /// + public async Task RunAsync() + { + IsRunning = true; + + var result = await TaskOperation(); + + IsRunning = false; + + if (!result.Succeeded) { - IsRunning = true; - - var result = await TaskOperation(); - - IsRunning = false; - - if (!result.Succeeded) - { - HasErrors = true; - - return result; - } - - IsCompleted = true; + HasErrors = true; return result; } - /// - /// The task you want to run - /// - /// - public abstract Task TaskOperation(); + IsCompleted = true; + + return result; } -} + + /// + /// The task you want to run + /// + /// + public abstract Task TaskOperation(); +} \ No newline at end of file diff --git a/SPTInstaller/Models/InternalData.cs b/SPTInstaller/Models/InternalData.cs index 0c93498..3f9f423 100644 --- a/SPTInstaller/Models/InternalData.cs +++ b/SPTInstaller/Models/InternalData.cs @@ -1,58 +1,56 @@ using System.Collections.Generic; -using System.IO; -namespace SPTInstaller.Models +namespace SPTInstaller.Models; + +public class InternalData { - public class InternalData - { - /// - /// The folder to install SPT into - /// - public string? TargetInstallPath { get; set; } + /// + /// The folder to install SPT into + /// + public string? TargetInstallPath { get; set; } - /// - /// The orginal EFT game path - /// - public string? OriginalGamePath { get; set; } + /// + /// The orginal EFT game path + /// + public string? OriginalGamePath { get; set; } - /// - /// The original EFT game version - /// - public string OriginalGameVersion { get; set; } + /// + /// The original EFT game version + /// + public string OriginalGameVersion { get; set; } - /// - /// Patcher zip file info - /// - public FileInfo PatcherZipInfo { get; set; } + /// + /// Patcher zip file info + /// + public FileInfo PatcherZipInfo { get; set; } - /// - /// SPT-AKI zip file info - /// - public FileInfo AkiZipInfo { get; set; } + /// + /// SPT-AKI zip file info + /// + public FileInfo AkiZipInfo { get; set; } - /// - /// The release download link for SPT-AKI - /// - public string AkiReleaseDownloadLink { get; set; } + /// + /// The release download link for SPT-AKI + /// + public string AkiReleaseDownloadLink { get; set; } - /// - /// The release zip hash - /// - public string AkiReleaseHash { get; set; } = null; + /// + /// The release zip hash + /// + public string AkiReleaseHash { get; set; } = null; - /// - /// The release download link for the patcher mirror list - /// - public string PatcherMirrorsLink { get; set; } + /// + /// The release download link for the patcher mirror list + /// + public string PatcherMirrorsLink { get; set; } - /// - /// The release download mirrors for the patcher - /// - public List PatcherReleaseMirrors { get; set; } = null; + /// + /// The release download mirrors for the patcher + /// + public List PatcherReleaseMirrors { get; set; } = null; - /// - /// Whether or not a patch is needed to downgrade the client files - /// - public bool PatchNeeded { get; set; } - } -} + /// + /// Whether or not a patch is needed to downgrade the client files + /// + public bool PatchNeeded { get; set; } +} \ No newline at end of file diff --git a/SPTInstaller/Models/PreCheckBase.cs b/SPTInstaller/Models/PreCheckBase.cs index 416986f..923b727 100644 --- a/SPTInstaller/Models/PreCheckBase.cs +++ b/SPTInstaller/Models/PreCheckBase.cs @@ -1,76 +1,74 @@ using ReactiveUI; using SPTInstaller.Interfaces; -using System; using System.Threading.Tasks; -namespace SPTInstaller.Models +namespace SPTInstaller.Models; + +public abstract class PreCheckBase : ReactiveObject, IPreCheck { - public abstract class PreCheckBase : ReactiveObject, IPreCheck + private string _id; + public string Id { - private string _id; - public string Id - { - get => _id; - set => this.RaiseAndSetIfChanged(ref _id, value); - } - - private string _name; - public string Name - { - get => _name; - set => this.RaiseAndSetIfChanged(ref _name, value); - } - - private bool _required; - public bool IsRequired - { - get => _required; - set => this.RaiseAndSetIfChanged(ref _required, value); - } - - private bool _passed; - public bool Passed - { - get => _passed; - set => this.RaiseAndSetIfChanged(ref _passed, value); - } - - private bool _isPending; - public bool IsPending - { - get => _isPending; - set => this.RaiseAndSetIfChanged(ref _isPending, value); - } - - private bool _isRunning; - public bool IsRunning - { - get => _isRunning; - set => this.RaiseAndSetIfChanged(ref _isRunning, value); - } - - /// - /// Base class for pre-checks to run before installation - /// - /// The display name of the pre-check - /// If installation should stop on failing this pre-check - public PreCheckBase(string name, bool required) - { - Name = name; - IsRequired = required; - Id = Guid.NewGuid().ToString(); - } - - public async Task RunCheck() - { - IsRunning = true; - Passed = await CheckOperation(); - IsRunning = false; - IsPending = false; - - return Passed ? Result.FromSuccess() : Result.FromError($"PreCheck Failed: {Name}"); - } - - public abstract Task CheckOperation(); + get => _id; + set => this.RaiseAndSetIfChanged(ref _id, value); } -} + + private string _name; + public string Name + { + get => _name; + set => this.RaiseAndSetIfChanged(ref _name, value); + } + + private bool _required; + public bool IsRequired + { + get => _required; + set => this.RaiseAndSetIfChanged(ref _required, value); + } + + private bool _passed; + public bool Passed + { + get => _passed; + set => this.RaiseAndSetIfChanged(ref _passed, value); + } + + private bool _isPending; + public bool IsPending + { + get => _isPending; + set => this.RaiseAndSetIfChanged(ref _isPending, value); + } + + private bool _isRunning; + public bool IsRunning + { + get => _isRunning; + set => this.RaiseAndSetIfChanged(ref _isRunning, value); + } + + /// + /// Base class for pre-checks to run before installation + /// + /// The display name of the pre-check + /// If installation should stop on failing this pre-check + public PreCheckBase(string name, bool required) + { + Name = name; + IsRequired = required; + Id = Guid.NewGuid().ToString(); + } + + public async Task RunCheck() + { + IsRunning = true; + Passed = await CheckOperation(); + IsRunning = false; + IsPending = false; + + return Passed ? Result.FromSuccess() : Result.FromError($"PreCheck Failed: {Name}"); + } + + public abstract Task CheckOperation(); +} \ No newline at end of file diff --git a/SPTInstaller/Models/Result.cs b/SPTInstaller/Models/Result.cs index bb520d0..d962e5c 100644 --- a/SPTInstaller/Models/Result.cs +++ b/SPTInstaller/Models/Result.cs @@ -1,20 +1,19 @@ using SPTInstaller.Interfaces; -namespace SPTInstaller.Models +namespace SPTInstaller.Models; + +public class Result : IResult { - public class Result : IResult + public bool Succeeded { get; private set; } + + public string Message { get; private set; } + + protected Result(string message, bool succeeded) { - public bool Succeeded { get; private set; } - - public string Message { get; private set; } - - protected Result(string message, bool succeeded) - { - Message = message; - Succeeded = succeeded; - } - - public static Result FromSuccess(string message = "") => new Result(message, true); - public static Result FromError(string message) => new Result(message, false); + Message = message; + Succeeded = succeeded; } -} + + public static Result FromSuccess(string message = "") => new(message, true); + public static Result FromError(string message) => new(message, false); +} \ No newline at end of file diff --git a/SPTInstaller/Program.cs b/SPTInstaller/Program.cs index 78e8dc9..ccc1b17 100644 --- a/SPTInstaller/Program.cs +++ b/SPTInstaller/Program.cs @@ -12,44 +12,44 @@ using SPTInstaller.Models; using System.Linq; using System.Reflection; -namespace SPTInstaller +namespace SPTInstaller; + +internal class Program { - internal class Program + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() { - // Initialization code. Don't use any Avalonia, third-party APIs or any - // SynchronizationContext-reliant code before AppMain is called: things aren't initialized - // yet and stuff might break. - [STAThread] - public static void Main(string[] args) => BuildAvaloniaApp() - .StartWithClassicDesktopLifetime(args); + Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly()); - // Avalonia configuration, don't remove; also used by visual designer. - public static AppBuilder BuildAvaloniaApp() - { - Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly()); - - // Register all the things - // Regestering as base classes so ReactiveUI works correctly. Doesn't seem to like the interfaces :( - ServiceHelper.Register(); - ServiceHelper.Register(); - ServiceHelper.Register(); - ServiceHelper.Register(); + // Register all the things + // Regestering as base classes so ReactiveUI works correctly. Doesn't seem to like the interfaces :( + ServiceHelper.Register(); + ServiceHelper.Register(); + ServiceHelper.Register(); + ServiceHelper.Register(); #if !TEST - var logPath = Path.Join(Environment.CurrentDirectory, "spt-aki-installer_.log"); + var logPath = Path.Join(Environment.CurrentDirectory, "spt-aki-installer_.log"); - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo - .File(path: logPath, - restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug, - rollingInterval: RollingInterval.Day) - .CreateLogger(); + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo + .File(path: logPath, + restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug, + rollingInterval: RollingInterval.Day) + .CreateLogger(); - ServiceHelper.Register(); - ServiceHelper.Register(); - ServiceHelper.Register(); - ServiceHelper.Register(); - ServiceHelper.Register(); + ServiceHelper.Register(); + ServiceHelper.Register(); + ServiceHelper.Register(); + ServiceHelper.Register(); + ServiceHelper.Register(); #else for (int i = 0; i < 5; i++) { @@ -57,19 +57,18 @@ namespace SPTInstaller } #endif - // need the interfaces for the controller and splat won't resolve them since we need to base classes in avalonia (what a mess), so doing it manually here - var tasks = Locator.Current.GetServices().ToArray() as IProgressableTask[]; - var preChecks = Locator.Current.GetServices().ToArray() as IPreCheck[]; + // need the interfaces for the controller and splat won't resolve them since we need to base classes in avalonia (what a mess), so doing it manually here + var tasks = Locator.Current.GetServices().ToArray() as IProgressableTask[]; + var preChecks = Locator.Current.GetServices().ToArray() as IPreCheck[]; - var installer = new InstallController(tasks, preChecks); + var installer = new InstallController(tasks, preChecks); - // manually register install controller - Locator.CurrentMutable.RegisterConstant(installer); + // manually register install controller + Locator.CurrentMutable.RegisterConstant(installer); - return AppBuilder.Configure() - .UsePlatformDetect() - .LogToTrace() - .UseReactiveUI(); - } + return AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace() + .UseReactiveUI(); } } \ No newline at end of file diff --git a/SPTInstaller/ViewLocator.cs b/SPTInstaller/ViewLocator.cs index bf62b2d..f33e897 100644 --- a/SPTInstaller/ViewLocator.cs +++ b/SPTInstaller/ViewLocator.cs @@ -1,28 +1,26 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; using SPTInstaller.ViewModels; -using System; -namespace SPTInstaller +namespace SPTInstaller; + +public class ViewLocator : IDataTemplate { - public class ViewLocator : IDataTemplate + public IControl Build(object data) { - public IControl Build(object data) + var name = data.GetType().FullName!.Replace("ViewModel", "View"); + var type = Type.GetType(name); + + if (type != null) { - var name = data.GetType().FullName!.Replace("ViewModel", "View"); - var type = Type.GetType(name); - - if (type != null) - { - return (Control)Activator.CreateInstance(type)!; - } - - return new TextBlock { Text = "Not Found: " + name }; + return (Control)Activator.CreateInstance(type)!; } - public bool Match(object data) - { - return data is ViewModelBase; - } + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object data) + { + return data is ViewModelBase; } } \ No newline at end of file diff --git a/SPTInstaller/ViewModels/InstallViewModel.cs b/SPTInstaller/ViewModels/InstallViewModel.cs index 80d0870..032e6bb 100644 --- a/SPTInstaller/ViewModels/InstallViewModel.cs +++ b/SPTInstaller/ViewModels/InstallViewModel.cs @@ -4,35 +4,32 @@ using SPTInstaller.Helpers; using SPTInstaller.Interfaces; using SPTInstaller.Models; using System.Collections.ObjectModel; -using System.Linq; using System.Threading.Tasks; -namespace SPTInstaller.ViewModels +namespace SPTInstaller.ViewModels; + +public class InstallViewModel : ViewModelBase { - public class InstallViewModel : ViewModelBase + private IProgressableTask _currentTask; + public IProgressableTask CurrentTask { - private IProgressableTask _currentTask; - public IProgressableTask CurrentTask - { - get => _currentTask; - set => this.RaiseAndSetIfChanged(ref _currentTask, value); - } - - public ObservableCollection MyTasks { get; set; } - = new ObservableCollection(ServiceHelper.GetAll()); - - public InstallViewModel(IScreen host) : base(host) - { - var installer = ServiceHelper.Get(); - - installer.TaskChanged += (sender, task) => CurrentTask = task; - - Task.Run(async () => - { - var result = await installer.RunTasks(); - - NavigateTo(new MessageViewModel(HostScreen, result)); - }); - } + get => _currentTask; + set => this.RaiseAndSetIfChanged(ref _currentTask, value); } -} + + public ObservableCollection MyTasks { get; set; } = new(ServiceHelper.GetAll()); + + public InstallViewModel(IScreen host) : base(host) + { + var installer = ServiceHelper.Get(); + + installer.TaskChanged += (sender, task) => CurrentTask = task; + + Task.Run(async () => + { + var result = await installer.RunTasks(); + + NavigateTo(new MessageViewModel(HostScreen, result)); + }); + } +} \ No newline at end of file diff --git a/SPTInstaller/ViewModels/MainWindowViewModel.cs b/SPTInstaller/ViewModels/MainWindowViewModel.cs index 793a76c..8b86439 100644 --- a/SPTInstaller/ViewModels/MainWindowViewModel.cs +++ b/SPTInstaller/ViewModels/MainWindowViewModel.cs @@ -1,50 +1,48 @@ using Avalonia; using ReactiveUI; using Serilog; -using System; using System.Reflection; -namespace SPTInstaller.ViewModels +namespace SPTInstaller.ViewModels; + +public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScreen { - public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScreen + public RoutingState Router { get; } = new(); + public ViewModelActivator Activator { get; } = new(); + + private string _title; + public string Title { - public RoutingState Router { get; } = new RoutingState(); - public ViewModelActivator Activator { get; } = new ViewModelActivator(); - - private string _title; - public string Title - { - get => _title; - set => this.RaiseAndSetIfChanged(ref _title, value); - } - - public MainWindowViewModel() - { - string? version = Assembly.GetExecutingAssembly().GetName()?.Version?.ToString(); - - Title = $"SPT Installer {"v" + version ?? "--unknown version--"}"; - - Log.Information($"========= {Title} Started ========="); - Log.Information(Environment.OSVersion.VersionString); - - Router.Navigate.Execute(new PreChecksViewModel(this)); - } - - public void CloseCommand() - { - if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp) - { - desktopApp.MainWindow.Close(); - } - } - - public void MinimizeCommand() - { - if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp) - { - desktopApp.MainWindow.WindowState = Avalonia.Controls.WindowState.Minimized; - } - } - + get => _title; + set => this.RaiseAndSetIfChanged(ref _title, value); } + + public MainWindowViewModel() + { + string? version = Assembly.GetExecutingAssembly().GetName()?.Version?.ToString(); + + Title = $"SPT Installer {"v" + version ?? "--unknown version--"}"; + + Log.Information($"========= {Title} Started ========="); + Log.Information(Environment.OSVersion.VersionString); + + Router.Navigate.Execute(new PreChecksViewModel(this)); + } + + public void CloseCommand() + { + if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp) + { + desktopApp.MainWindow.Close(); + } + } + + public void MinimizeCommand() + { + if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp) + { + desktopApp.MainWindow.WindowState = Avalonia.Controls.WindowState.Minimized; + } + } + } \ No newline at end of file diff --git a/SPTInstaller/ViewModels/MessageViewModel.cs b/SPTInstaller/ViewModels/MessageViewModel.cs index 7591382..ab8c097 100644 --- a/SPTInstaller/ViewModels/MessageViewModel.cs +++ b/SPTInstaller/ViewModels/MessageViewModel.cs @@ -4,44 +4,43 @@ using Serilog; using SPTInstaller.Interfaces; using System.Windows.Input; -namespace SPTInstaller.ViewModels +namespace SPTInstaller.ViewModels; + +public class MessageViewModel : ViewModelBase { - public class MessageViewModel : ViewModelBase + private bool _HasErrors; + public bool HasErrors { - private bool _HasErrors; - public bool HasErrors - { - get => _HasErrors; - set => this.RaiseAndSetIfChanged(ref _HasErrors, value); - } - - private string _Message; - public string Message - { - get => _Message; - set => this.RaiseAndSetIfChanged(ref _Message, value); - } - - public ICommand CloseCommand { get; set; } = ReactiveCommand.Create(() => - { - if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp) - { - desktopApp.MainWindow.Close(); - } - }); - - public MessageViewModel(IScreen Host, IResult result) : base(Host) - { - Message = result.Message; - - if(result.Succeeded) - { - Log.Information(Message); - return; - } - - HasErrors = true; - Log.Error(Message); - } + get => _HasErrors; + set => this.RaiseAndSetIfChanged(ref _HasErrors, value); } -} + + private string _Message; + public string Message + { + get => _Message; + set => this.RaiseAndSetIfChanged(ref _Message, value); + } + + public ICommand CloseCommand { get; set; } = ReactiveCommand.Create(() => + { + if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp) + { + desktopApp.MainWindow.Close(); + } + }); + + public MessageViewModel(IScreen Host, IResult result) : base(Host) + { + Message = result.Message; + + if(result.Succeeded) + { + Log.Information(Message); + return; + } + + HasErrors = true; + Log.Error(Message); + } +} \ No newline at end of file diff --git a/SPTInstaller/ViewModels/ViewModelBase.cs b/SPTInstaller/ViewModels/ViewModelBase.cs index 8d85c91..32728a7 100644 --- a/SPTInstaller/ViewModels/ViewModelBase.cs +++ b/SPTInstaller/ViewModels/ViewModelBase.cs @@ -1,61 +1,58 @@ using Avalonia.Threading; using ReactiveUI; using System.Threading.Tasks; -using System; -namespace SPTInstaller.ViewModels +namespace SPTInstaller.ViewModels; + +public class ViewModelBase : ReactiveObject, IActivatableViewModel, IRoutableViewModel { - public class ViewModelBase : ReactiveObject, IActivatableViewModel, IRoutableViewModel + public ViewModelActivator Activator { get; } = new(); + + public string? UrlPathSegment => Guid.NewGuid().ToString().Substring(0, 7); + + public IScreen HostScreen { get; } + + /// + /// Delay the return of the viewmodel + /// + /// The amount of time in milliseconds to delay + /// The viewmodel after the delay time + /// Useful to delay the navigation to another view. For instance, to allow an animation to complete. + private async Task WithDelay(int Milliseconds) { - public ViewModelActivator Activator { get; } = new ViewModelActivator(); + await Task.Delay(Milliseconds); - public string? UrlPathSegment => Guid.NewGuid().ToString().Substring(0, 7); - - public IScreen HostScreen { get; } - - /// - /// Delay the return of the viewmodel - /// - /// The amount of time in milliseconds to delay - /// The viewmodel after the delay time - /// Useful to delay the navigation to another view. For instance, to allow an animation to complete. - private async Task WithDelay(int Milliseconds) - { - await Task.Delay(Milliseconds); - - return this; - } - - /// - /// Navigate to another viewmodel after a delay - /// - /// - /// - /// - public async Task NavigateToWithDelay(ViewModelBase ViewModel, int Milliseconds) - { - await Dispatcher.UIThread.InvokeAsync(async () => - { - HostScreen.Router.Navigate.Execute(await ViewModel.WithDelay(Milliseconds)); - }); - } - - /// - /// Navigate to another viewmodel - /// - /// - public void NavigateTo(ViewModelBase ViewModel) - { - Dispatcher.UIThread.InvokeAsync(() => - { - HostScreen.Router.Navigate.Execute(ViewModel); - }); - } - - public ViewModelBase(IScreen Host) - { - HostScreen = Host; - } + return this; } + /// + /// Navigate to another viewmodel after a delay + /// + /// + /// + /// + public async Task NavigateToWithDelay(ViewModelBase ViewModel, int Milliseconds) + { + await Dispatcher.UIThread.InvokeAsync(async () => + { + HostScreen.Router.Navigate.Execute(await ViewModel.WithDelay(Milliseconds)); + }); + } + + /// + /// Navigate to another viewmodel + /// + /// + public void NavigateTo(ViewModelBase ViewModel) + { + Dispatcher.UIThread.InvokeAsync(() => + { + HostScreen.Router.Navigate.Execute(ViewModel); + }); + } + + public ViewModelBase(IScreen Host) + { + HostScreen = Host; + } } \ No newline at end of file diff --git a/SPTInstaller/Views/InstallView.axaml.cs b/SPTInstaller/Views/InstallView.axaml.cs index 813f5f0..1b8a5b7 100644 --- a/SPTInstaller/Views/InstallView.axaml.cs +++ b/SPTInstaller/Views/InstallView.axaml.cs @@ -1,14 +1,12 @@ -using Avalonia.Controls; using Avalonia.ReactiveUI; using SPTInstaller.ViewModels; -namespace SPTInstaller.Views +namespace SPTInstaller.Views; + +public partial class InstallView : ReactiveUserControl { - public partial class InstallView : ReactiveUserControl + public InstallView() { - public InstallView() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/SPTInstaller/Views/MainWindow.axaml.cs b/SPTInstaller/Views/MainWindow.axaml.cs index edc3580..1e69f06 100644 --- a/SPTInstaller/Views/MainWindow.axaml.cs +++ b/SPTInstaller/Views/MainWindow.axaml.cs @@ -1,12 +1,11 @@ using Avalonia.Controls; -namespace SPTInstaller.Views +namespace SPTInstaller.Views; + +public partial class MainWindow : Window { - public partial class MainWindow : Window + public MainWindow() { - public MainWindow() - { - InitializeComponent(); - } + InitializeComponent(); } } \ No newline at end of file diff --git a/SPTInstaller/Views/MessageView.axaml.cs b/SPTInstaller/Views/MessageView.axaml.cs index 9d37338..c89811d 100644 --- a/SPTInstaller/Views/MessageView.axaml.cs +++ b/SPTInstaller/Views/MessageView.axaml.cs @@ -1,14 +1,12 @@ -using Avalonia.Controls; using Avalonia.ReactiveUI; using SPTInstaller.ViewModels; -namespace SPTInstaller.Views +namespace SPTInstaller.Views; + +public partial class MessageView : ReactiveUserControl { - public partial class MessageView : ReactiveUserControl + public MessageView() { - public MessageView() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file diff --git a/SPTInstaller/Views/PreChecksView.axaml.cs b/SPTInstaller/Views/PreChecksView.axaml.cs index db0142e..ffc4b0b 100644 --- a/SPTInstaller/Views/PreChecksView.axaml.cs +++ b/SPTInstaller/Views/PreChecksView.axaml.cs @@ -1,13 +1,12 @@ using Avalonia.ReactiveUI; using SPTInstaller.ViewModels; -namespace SPTInstaller.Views +namespace SPTInstaller.Views; + +public partial class PreChecksView : ReactiveUserControl { - public partial class PreChecksView : ReactiveUserControl + public PreChecksView() { - public PreChecksView() - { - InitializeComponent(); - } + InitializeComponent(); } -} +} \ No newline at end of file From c98c59edf24e83c47f3de455bdef046c8b6e57a5 Mon Sep 17 00:00:00 2001 From: Philipp Heenemann Date: Wed, 12 Jul 2023 09:29:02 +0200 Subject: [PATCH 2/5] Refactor InvertedProgressConverter for readability --- SPTInstaller/Converters/InvertedProgressConverter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SPTInstaller/Converters/InvertedProgressConverter.cs b/SPTInstaller/Converters/InvertedProgressConverter.cs index 5ee62d8..6176f47 100644 --- a/SPTInstaller/Converters/InvertedProgressConverter.cs +++ b/SPTInstaller/Converters/InvertedProgressConverter.cs @@ -1,5 +1,5 @@ -using Avalonia.Data.Converters; -using System.Globalization; +using System.Globalization; +using Avalonia.Data.Converters; namespace SPTInstaller.Converters; @@ -7,7 +7,7 @@ public class InvertedProgressConverter : IValueConverter { public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if( value is int progress) + if (value is int progress) { return 100 - progress; } @@ -17,7 +17,7 @@ public class InvertedProgressConverter : IValueConverter public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { - if ( value is int invertedProgress) + if (value is int invertedProgress) { return 100 - invertedProgress; } From 4414b00ec44698b33f77ccf5255391e1019ea9df Mon Sep 17 00:00:00 2001 From: Philipp Heenemann Date: Wed, 12 Jul 2023 17:40:44 +0200 Subject: [PATCH 3/5] Add .editorconfig --- .editorconfig | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a1600ee --- /dev/null +++ b/.editorconfig @@ -0,0 +1,57 @@ + +[*] +charset = utf-8-bom +end_of_line = crlf +trim_trailing_whitespace = false +insert_final_newline = false +indent_style = space +indent_size = 4 + +# Microsoft .NET properties +csharp_style_namespace_declarations=file_scoped:suggestion +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper properties +resharper_autodetect_indent_settings = true +resharper_enforce_line_ending_style = true +resharper_formatter_off_tag = @formatter:off +resharper_formatter_on_tag = @formatter:on +resharper_formatter_tags_enabled = true +resharper_use_indent_from_vs = false +resharper_wrap_lines = true + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_redundant_base_qualifier_highlighting = warning +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cppm,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 From 8de68a7b835b69d5bdd5fe75b5656bb89ec6f020 Mon Sep 17 00:00:00 2001 From: Philipp Heenemann Date: Sun, 16 Jul 2023 19:21:03 +0200 Subject: [PATCH 4/5] Update C# style rule severity in .editorconfig --- .editorconfig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index a1600ee..2287241 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,13 +8,13 @@ indent_style = space indent_size = 4 # Microsoft .NET properties -csharp_style_namespace_declarations=file_scoped:suggestion +csharp_style_namespace_declarations=file_scoped:error csharp_new_line_before_members_in_object_initializers = false -csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:suggestion +csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:warning csharp_style_prefer_utf8_string_literals = true:suggestion csharp_style_var_elsewhere = true:suggestion -csharp_style_var_for_built_in_types = true:suggestion -csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning dotnet_naming_style.lower_camel_case_style.capitalization = camel_case dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none From a96fd15ef4256f460900504bac4f5984a7274bbb Mon Sep 17 00:00:00 2001 From: Philipp Heenemann Date: Sun, 16 Jul 2023 19:22:39 +0200 Subject: [PATCH 5/5] Remove unused import in PreChecksViewModel --- SPTInstaller/ViewModels/PreChecksViewModel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SPTInstaller/ViewModels/PreChecksViewModel.cs b/SPTInstaller/ViewModels/PreChecksViewModel.cs index 5718b96..a448d55 100644 --- a/SPTInstaller/ViewModels/PreChecksViewModel.cs +++ b/SPTInstaller/ViewModels/PreChecksViewModel.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using System.Windows.Input; using ReactiveUI; -using SPTInstaller.Aki.Helper; using SPTInstaller.Controllers; using SPTInstaller.Helpers; using SPTInstaller.Models;