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.
This commit is contained in:
parent
d6aaeda28c
commit
a8b91f4ee6
@ -4,26 +4,25 @@ using Avalonia.Markup.Xaml;
|
|||||||
using SPTInstaller.ViewModels;
|
using SPTInstaller.ViewModels;
|
||||||
using SPTInstaller.Views;
|
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(),
|
||||||
{
|
};
|
||||||
DataContext = new MainWindowViewModel(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,20 +1,19 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
namespace SPTInstaller.Behaviors
|
namespace SPTInstaller.Behaviors;
|
||||||
|
|
||||||
|
public class SpanBehavior : AvaloniaObject
|
||||||
{
|
{
|
||||||
public class SpanBehavior : AvaloniaObject
|
public static readonly AttachedProperty<bool> SpanProperty = AvaloniaProperty.RegisterAttached<SpanBehavior, Interactive, bool>("Span");
|
||||||
|
|
||||||
|
public static void SetSpan(AvaloniaObject element, bool value)
|
||||||
{
|
{
|
||||||
public static readonly AttachedProperty<bool> SpanProperty = AvaloniaProperty.RegisterAttached<SpanBehavior, Interactive, bool>("Span");
|
element.SetValue(SpanProperty, value);
|
||||||
|
|
||||||
public static void SetSpan(AvaloniaObject element, bool value)
|
|
||||||
{
|
|
||||||
element.SetValue(SpanProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool GetSpan(AvaloniaObject element)
|
|
||||||
{
|
|
||||||
return element.GetValue(SpanProperty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static bool GetSpan(AvaloniaObject element)
|
||||||
|
{
|
||||||
|
return element.GetValue(SpanProperty);
|
||||||
|
}
|
||||||
|
}
|
@ -2,67 +2,65 @@
|
|||||||
using SharpCompress;
|
using SharpCompress;
|
||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SPTInstaller.Controllers
|
namespace SPTInstaller.Controllers;
|
||||||
|
|
||||||
|
public class InstallController
|
||||||
{
|
{
|
||||||
public class InstallController
|
public event EventHandler<IProgressableTask> TaskChanged = delegate { };
|
||||||
|
|
||||||
|
private IPreCheck[] _preChecks { get; set; }
|
||||||
|
private IProgressableTask[] _tasks { get; set; }
|
||||||
|
|
||||||
|
public InstallController(IProgressableTask[] tasks, IPreCheck[] preChecks = null)
|
||||||
{
|
{
|
||||||
public event EventHandler<IProgressableTask> TaskChanged = delegate { };
|
_tasks = tasks;
|
||||||
|
_preChecks = preChecks;
|
||||||
private IPreCheck[] _preChecks { get; set; }
|
|
||||||
private IProgressableTask[] _tasks { get; set; }
|
|
||||||
|
|
||||||
public InstallController(IProgressableTask[] tasks, IPreCheck[] preChecks = null)
|
|
||||||
{
|
|
||||||
_tasks = tasks;
|
|
||||||
_preChecks = preChecks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IResult> RunPreChecks()
|
|
||||||
{
|
|
||||||
Log.Information("-<>--<>- Running PreChecks -<>--<>-");
|
|
||||||
var requiredResults = new List<IResult>();
|
|
||||||
|
|
||||||
_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<IResult> 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!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public async Task<IResult> RunPreChecks()
|
||||||
|
{
|
||||||
|
Log.Information("-<>--<>- Running PreChecks -<>--<>-");
|
||||||
|
var requiredResults = new List<IResult>();
|
||||||
|
|
||||||
|
_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<IResult> 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!");
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +1,27 @@
|
|||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
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 100 - progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
return value;
|
||||||
{
|
|
||||||
if ( value is int invertedProgress)
|
|
||||||
{
|
|
||||||
return 100 - invertedProgress;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if ( value is int invertedProgress)
|
||||||
|
{
|
||||||
|
return 100 - invertedProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -1,77 +1,75 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using SPTInstaller.Behaviors;
|
using SPTInstaller.Behaviors;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
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;
|
// measure child objects so we can use their desired size in the arrange override
|
||||||
|
var child = children[i];
|
||||||
for (int i = 0; i < children.Count; i++)
|
child.Measure(availableSize);
|
||||||
{
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
var child = children[i];
|
||||||
Rect rcChild = new Rect(finalSize);
|
|
||||||
double previousChildSize = 0.0;
|
|
||||||
|
|
||||||
// get child objects that don't want to span the entire control
|
var spanChild = child.GetValue(SpanBehavior.SpanProperty);
|
||||||
var nonSpanningChildren = children.Where(x => x.GetValue(SpanBehavior.SpanProperty) == false).ToList();
|
|
||||||
|
|
||||||
// get the total height off all non-spanning child objects
|
if (spanChild)
|
||||||
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 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);
|
rcChild = rcChild.WithY(0)
|
||||||
|
.WithX(0)
|
||||||
if (spanChild)
|
.WithHeight(finalSize.Height)
|
||||||
{
|
.WithWidth(finalSize.Width);
|
||||||
// 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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,62 +1,57 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
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();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string PreCheckName
|
|
||||||
{
|
|
||||||
get => GetValue(PreCheckNameProperty);
|
|
||||||
set => SetValue(PreCheckNameProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> PreCheckNameProperty =
|
|
||||||
AvaloniaProperty.Register<PreCheckItem, string>(nameof(PreCheckName));
|
|
||||||
|
|
||||||
public bool IsRunning
|
|
||||||
{
|
|
||||||
get => GetValue(IsRunningProperty);
|
|
||||||
set => SetValue(IsRunningProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsRunningProperty =
|
|
||||||
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(IsRunning));
|
|
||||||
|
|
||||||
public bool IsPending
|
|
||||||
{
|
|
||||||
get => GetValue(IsPendingProperty);
|
|
||||||
set => SetValue(IsPendingProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsPendingProperty =
|
|
||||||
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(IsPending));
|
|
||||||
|
|
||||||
public bool Passed
|
|
||||||
{
|
|
||||||
get => GetValue(PassedProperty);
|
|
||||||
set => SetValue(PassedProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> PassedProperty =
|
|
||||||
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(Passed));
|
|
||||||
|
|
||||||
public bool IsRequired
|
|
||||||
{
|
|
||||||
get => GetValue(IsRequiredProperty);
|
|
||||||
set => SetValue(IsRequiredProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsRequiredProperty =
|
|
||||||
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(IsRequired));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public string PreCheckName
|
||||||
|
{
|
||||||
|
get => GetValue(PreCheckNameProperty);
|
||||||
|
set => SetValue(PreCheckNameProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> PreCheckNameProperty =
|
||||||
|
AvaloniaProperty.Register<PreCheckItem, string>(nameof(PreCheckName));
|
||||||
|
|
||||||
|
public bool IsRunning
|
||||||
|
{
|
||||||
|
get => GetValue(IsRunningProperty);
|
||||||
|
set => SetValue(IsRunningProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsRunningProperty =
|
||||||
|
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(IsRunning));
|
||||||
|
|
||||||
|
public bool IsPending
|
||||||
|
{
|
||||||
|
get => GetValue(IsPendingProperty);
|
||||||
|
set => SetValue(IsPendingProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsPendingProperty =
|
||||||
|
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(IsPending));
|
||||||
|
|
||||||
|
public bool Passed
|
||||||
|
{
|
||||||
|
get => GetValue(PassedProperty);
|
||||||
|
set => SetValue(PassedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> PassedProperty =
|
||||||
|
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(Passed));
|
||||||
|
|
||||||
|
public bool IsRequired
|
||||||
|
{
|
||||||
|
get => GetValue(IsRequiredProperty);
|
||||||
|
set => SetValue(IsRequiredProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsRequiredProperty =
|
||||||
|
AvaloniaProperty.Register<PreCheckItem, bool>(nameof(IsRequired));
|
||||||
|
}
|
@ -2,76 +2,75 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
|
||||||
namespace SPTInstaller.CustomControls
|
namespace SPTInstaller.CustomControls;
|
||||||
|
|
||||||
|
public partial class ProgressableTaskItem : UserControl
|
||||||
{
|
{
|
||||||
public partial class ProgressableTaskItem : UserControl
|
public ProgressableTaskItem()
|
||||||
{
|
{
|
||||||
public ProgressableTaskItem()
|
InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string TaskId
|
|
||||||
{
|
|
||||||
get => GetValue(TaskIdProperty);
|
|
||||||
set => SetValue(TaskIdProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> TaskIdProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskItem, string>(nameof(TaskId));
|
|
||||||
|
|
||||||
public string TaskName
|
|
||||||
{
|
|
||||||
get => GetValue(TaskNameProperty);
|
|
||||||
set => SetValue(TaskNameProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> TaskNameProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskItem, string>(nameof(TaskName));
|
|
||||||
|
|
||||||
public bool IsCompleted
|
|
||||||
{
|
|
||||||
get => GetValue(IsCompletedProperty);
|
|
||||||
set => SetValue(IsCompletedProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsCompletedProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskItem, bool>(nameof(IsCompleted));
|
|
||||||
|
|
||||||
public bool IsRunning
|
|
||||||
{
|
|
||||||
get => GetValue(IsRunningProperty);
|
|
||||||
set => SetValue(IsRunningProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsRunningProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskItem, bool>(nameof(IsRunning));
|
|
||||||
|
|
||||||
public IBrush PendingColor
|
|
||||||
{
|
|
||||||
get => GetValue(PendingColorProperty);
|
|
||||||
set => SetValue(PendingColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> PendingColorProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskItem, IBrush>(nameof(PendingColor));
|
|
||||||
|
|
||||||
public IBrush RunningColor
|
|
||||||
{
|
|
||||||
get => GetValue(RunningColorProperty);
|
|
||||||
set => SetValue(RunningColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> RunningColorProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskItem, IBrush>(nameof(PendingColor));
|
|
||||||
|
|
||||||
public IBrush CompletedColor
|
|
||||||
{
|
|
||||||
get => GetValue(CompletedColorProperty);
|
|
||||||
set => SetValue(CompletedColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> CompletedColorProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskItem, IBrush>(nameof(PendingColor));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public string TaskId
|
||||||
|
{
|
||||||
|
get => GetValue(TaskIdProperty);
|
||||||
|
set => SetValue(TaskIdProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> TaskIdProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskItem, string>(nameof(TaskId));
|
||||||
|
|
||||||
|
public string TaskName
|
||||||
|
{
|
||||||
|
get => GetValue(TaskNameProperty);
|
||||||
|
set => SetValue(TaskNameProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> TaskNameProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskItem, string>(nameof(TaskName));
|
||||||
|
|
||||||
|
public bool IsCompleted
|
||||||
|
{
|
||||||
|
get => GetValue(IsCompletedProperty);
|
||||||
|
set => SetValue(IsCompletedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsCompletedProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskItem, bool>(nameof(IsCompleted));
|
||||||
|
|
||||||
|
public bool IsRunning
|
||||||
|
{
|
||||||
|
get => GetValue(IsRunningProperty);
|
||||||
|
set => SetValue(IsRunningProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsRunningProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskItem, bool>(nameof(IsRunning));
|
||||||
|
|
||||||
|
public IBrush PendingColor
|
||||||
|
{
|
||||||
|
get => GetValue(PendingColorProperty);
|
||||||
|
set => SetValue(PendingColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> PendingColorProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskItem, IBrush>(nameof(PendingColor));
|
||||||
|
|
||||||
|
public IBrush RunningColor
|
||||||
|
{
|
||||||
|
get => GetValue(RunningColorProperty);
|
||||||
|
set => SetValue(RunningColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> RunningColorProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskItem, IBrush>(nameof(PendingColor));
|
||||||
|
|
||||||
|
public IBrush CompletedColor
|
||||||
|
{
|
||||||
|
get => GetValue(CompletedColorProperty);
|
||||||
|
set => SetValue(CompletedColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> CompletedColorProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskItem, IBrush>(nameof(PendingColor));
|
||||||
|
}
|
@ -2,96 +2,93 @@ using Avalonia;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DynamicData;
|
|
||||||
using DynamicData.Binding;
|
using DynamicData.Binding;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
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<ProgressableTaskList, int> TaskProgressProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<ProgressableTaskList, int>(nameof(TaskProgress), o => o.TaskProgress);
|
||||||
|
|
||||||
|
public ObservableCollection<InstallerTaskBase> Tasks
|
||||||
|
{
|
||||||
|
get => GetValue(TasksProperty);
|
||||||
|
set => SetValue(TasksProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<ObservableCollection<InstallerTaskBase>> TasksProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskList, ObservableCollection<InstallerTaskBase>>(nameof(Tasks));
|
||||||
|
|
||||||
|
public IBrush PendingColor
|
||||||
|
{
|
||||||
|
get => GetValue(PendingColorProperty);
|
||||||
|
set => SetValue(PendingColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> PendingColorProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskList, IBrush>(nameof(PendingColor));
|
||||||
|
|
||||||
|
public IBrush RunningColor
|
||||||
|
{
|
||||||
|
get => GetValue(RunningColorProperty);
|
||||||
|
set => SetValue(RunningColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> RunningColorProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskList, IBrush>(nameof(PendingColor));
|
||||||
|
|
||||||
|
public IBrush CompletedColor
|
||||||
|
{
|
||||||
|
get => GetValue(CompletedColorProperty);
|
||||||
|
set => SetValue(CompletedColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> CompletedColorProperty =
|
||||||
|
AvaloniaProperty.Register<ProgressableTaskList, IBrush>(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;
|
for(; TaskProgress < progress;)
|
||||||
public int TaskProgress
|
|
||||||
{
|
|
||||||
get => _taskProgress;
|
|
||||||
set => SetAndRaise(ProgressableTaskList.TaskProgressProperty, ref _taskProgress, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly DirectProperty<ProgressableTaskList, int> TaskProgressProperty =
|
|
||||||
AvaloniaProperty.RegisterDirect<ProgressableTaskList, int>(nameof(TaskProgress), o => o.TaskProgress);
|
|
||||||
|
|
||||||
public ObservableCollection<InstallerTaskBase> Tasks
|
|
||||||
{
|
|
||||||
get => GetValue(TasksProperty);
|
|
||||||
set => SetValue(TasksProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<ObservableCollection<InstallerTaskBase>> TasksProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskList, ObservableCollection<InstallerTaskBase>>(nameof(Tasks));
|
|
||||||
|
|
||||||
public IBrush PendingColor
|
|
||||||
{
|
|
||||||
get => GetValue(PendingColorProperty);
|
|
||||||
set => SetValue(PendingColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> PendingColorProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskList, IBrush>(nameof(PendingColor));
|
|
||||||
|
|
||||||
public IBrush RunningColor
|
|
||||||
{
|
|
||||||
get => GetValue(RunningColorProperty);
|
|
||||||
set => SetValue(RunningColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> RunningColorProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskList, IBrush>(nameof(PendingColor));
|
|
||||||
|
|
||||||
public IBrush CompletedColor
|
|
||||||
{
|
|
||||||
get => GetValue(CompletedColorProperty);
|
|
||||||
set => SetValue(CompletedColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> CompletedColorProperty =
|
|
||||||
AvaloniaProperty.Register<ProgressableTaskList, IBrush>(nameof(PendingColor));
|
|
||||||
|
|
||||||
private void UpdateTaskProgress()
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
{
|
||||||
var completedTasks = Tasks.Where(x => x.IsCompleted == true).Count();
|
TaskProgress += 1;
|
||||||
|
await Task.Delay(1);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,58 +1,57 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace SPTInstaller.CustomControls
|
namespace SPTInstaller.CustomControls;
|
||||||
|
|
||||||
|
public partial class TaskDetails : UserControl
|
||||||
{
|
{
|
||||||
public partial class TaskDetails : UserControl
|
public TaskDetails()
|
||||||
{
|
{
|
||||||
public TaskDetails()
|
InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Message
|
|
||||||
{
|
|
||||||
get => GetValue(MessageProperty);
|
|
||||||
set => SetValue(MessageProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> MessageProperty =
|
|
||||||
AvaloniaProperty.Register<TaskDetails, string>(nameof(Message));
|
|
||||||
|
|
||||||
public string Details
|
|
||||||
{
|
|
||||||
get => GetValue(DetailsProperty);
|
|
||||||
set => SetValue(DetailsProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> DetailsProperty =
|
|
||||||
AvaloniaProperty.Register<TaskDetails, string>(nameof(Details));
|
|
||||||
|
|
||||||
public int Progress
|
|
||||||
{
|
|
||||||
get => GetValue(ProgressProperty);
|
|
||||||
set => SetValue(ProgressProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<int> ProgressProperty =
|
|
||||||
AvaloniaProperty.Register<TaskDetails, int>(nameof(Progress));
|
|
||||||
|
|
||||||
public bool ShowProgress
|
|
||||||
{
|
|
||||||
get => GetValue(ShowProgressProperty);
|
|
||||||
set => SetValue(ShowProgressProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShowProgressProperty =
|
|
||||||
AvaloniaProperty.Register<TaskDetails, bool>(nameof(ShowProgress));
|
|
||||||
|
|
||||||
public bool IndeterminateProgress
|
|
||||||
{
|
|
||||||
get => GetValue(IndeterminateProgressProperty);
|
|
||||||
set => SetValue(IndeterminateProgressProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IndeterminateProgressProperty =
|
|
||||||
AvaloniaProperty.Register<TaskDetails, bool>(nameof(IndeterminateProgress));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public string Message
|
||||||
|
{
|
||||||
|
get => GetValue(MessageProperty);
|
||||||
|
set => SetValue(MessageProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> MessageProperty =
|
||||||
|
AvaloniaProperty.Register<TaskDetails, string>(nameof(Message));
|
||||||
|
|
||||||
|
public string Details
|
||||||
|
{
|
||||||
|
get => GetValue(DetailsProperty);
|
||||||
|
set => SetValue(DetailsProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> DetailsProperty =
|
||||||
|
AvaloniaProperty.Register<TaskDetails, string>(nameof(Details));
|
||||||
|
|
||||||
|
public int Progress
|
||||||
|
{
|
||||||
|
get => GetValue(ProgressProperty);
|
||||||
|
set => SetValue(ProgressProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<int> ProgressProperty =
|
||||||
|
AvaloniaProperty.Register<TaskDetails, int>(nameof(Progress));
|
||||||
|
|
||||||
|
public bool ShowProgress
|
||||||
|
{
|
||||||
|
get => GetValue(ShowProgressProperty);
|
||||||
|
set => SetValue(ShowProgressProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> ShowProgressProperty =
|
||||||
|
AvaloniaProperty.Register<TaskDetails, bool>(nameof(ShowProgress));
|
||||||
|
|
||||||
|
public bool IndeterminateProgress
|
||||||
|
{
|
||||||
|
get => GetValue(IndeterminateProgressProperty);
|
||||||
|
set => SetValue(IndeterminateProgressProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IndeterminateProgressProperty =
|
||||||
|
AvaloniaProperty.Register<TaskDetails, bool>(nameof(IndeterminateProgress));
|
||||||
|
}
|
@ -4,74 +4,73 @@ using Avalonia.Markup.Xaml;
|
|||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using System.Windows.Input;
|
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();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> TitleProperty =
|
|
||||||
AvaloniaProperty.Register<TitleBar, string>(nameof(Title));
|
|
||||||
|
|
||||||
public string Title
|
|
||||||
{
|
|
||||||
get => GetValue(TitleProperty);
|
|
||||||
set => SetValue(TitleProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> ButtonForegroundProperty =
|
|
||||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(ButtonForeground));
|
|
||||||
|
|
||||||
public IBrush ButtonForeground
|
|
||||||
{
|
|
||||||
get => GetValue(ButtonForegroundProperty);
|
|
||||||
set => SetValue(ButtonForegroundProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static new readonly StyledProperty<IBrush> ForegroundProperty =
|
|
||||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Foreground));
|
|
||||||
|
|
||||||
public new IBrush Foreground
|
|
||||||
{
|
|
||||||
get => GetValue(ForegroundProperty);
|
|
||||||
set => SetValue(ForegroundProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static new readonly StyledProperty<IBrush> BackgroundProperty =
|
|
||||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Background));
|
|
||||||
|
|
||||||
public new IBrush Background
|
|
||||||
{
|
|
||||||
get => GetValue(BackgroundProperty);
|
|
||||||
set => SetValue(BackgroundProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Close Button Command (X Button) Property
|
|
||||||
public static readonly StyledProperty<ICommand> XButtonCommandProperty =
|
|
||||||
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(XButtonCommand));
|
|
||||||
|
|
||||||
public ICommand XButtonCommand
|
|
||||||
{
|
|
||||||
get => GetValue(XButtonCommandProperty);
|
|
||||||
set => SetValue(XButtonCommandProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Minimize Button Command (- Button) Property
|
|
||||||
public static readonly StyledProperty<ICommand> MinButtonCommandProperty =
|
|
||||||
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(MinButtonCommand));
|
|
||||||
|
|
||||||
public ICommand MinButtonCommand
|
|
||||||
{
|
|
||||||
get => GetValue(MinButtonCommandProperty);
|
|
||||||
set => SetValue(MinButtonCommandProperty, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> TitleProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, string>(nameof(Title));
|
||||||
|
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get => GetValue(TitleProperty);
|
||||||
|
set => SetValue(TitleProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> ButtonForegroundProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(ButtonForeground));
|
||||||
|
|
||||||
|
public IBrush ButtonForeground
|
||||||
|
{
|
||||||
|
get => GetValue(ButtonForegroundProperty);
|
||||||
|
set => SetValue(ButtonForegroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static new readonly StyledProperty<IBrush> ForegroundProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Foreground));
|
||||||
|
|
||||||
|
public new IBrush Foreground
|
||||||
|
{
|
||||||
|
get => GetValue(ForegroundProperty);
|
||||||
|
set => SetValue(ForegroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static new readonly StyledProperty<IBrush> BackgroundProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Background));
|
||||||
|
|
||||||
|
public new IBrush Background
|
||||||
|
{
|
||||||
|
get => GetValue(BackgroundProperty);
|
||||||
|
set => SetValue(BackgroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close Button Command (X Button) Property
|
||||||
|
public static readonly StyledProperty<ICommand> XButtonCommandProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(XButtonCommand));
|
||||||
|
|
||||||
|
public ICommand XButtonCommand
|
||||||
|
{
|
||||||
|
get => GetValue(XButtonCommandProperty);
|
||||||
|
set => SetValue(XButtonCommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Minimize Button Command (- Button) Property
|
||||||
|
public static readonly StyledProperty<ICommand> MinButtonCommandProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(MinButtonCommand));
|
||||||
|
|
||||||
|
public ICommand MinButtonCommand
|
||||||
|
{
|
||||||
|
get => GetValue(MinButtonCommandProperty);
|
||||||
|
set => SetValue(MinButtonCommandProperty, value);
|
||||||
|
}
|
||||||
|
}
|
@ -1,146 +1,142 @@
|
|||||||
using HttpClientProgress;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SPTInstaller.Models;
|
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) };
|
try
|
||||||
|
|
||||||
private static string _cachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache");
|
|
||||||
|
|
||||||
private static bool CheckCache(FileInfo cacheFile, string expectedHash = null)
|
|
||||||
{
|
{
|
||||||
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();
|
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;
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
private static async Task<Result> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
|
||||||
{
|
{
|
||||||
try
|
return false;
|
||||||
{
|
|
||||||
// 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<Result> 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<Result> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> 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<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> 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<FileInfo?> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private static async Task<Result> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> 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<Result> 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<Result> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> 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<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> 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<FileInfo?> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +1,34 @@
|
|||||||
using Gitea.Model;
|
using System.Linq;
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text.RegularExpressions;
|
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: (?<hash>\S+)");
|
||||||
|
|
||||||
|
if (regex.Success)
|
||||||
{
|
{
|
||||||
var regex = Regex.Match(release.Body, @"Release Hash: (?<hash>\S+)");
|
return regex.Groups["hash"].Value;
|
||||||
|
|
||||||
if (regex.Success)
|
|
||||||
{
|
|
||||||
return regex.Groups["hash"].Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CheckHash(FileInfo file, string expectedHash)
|
return null;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,90 +1,86 @@
|
|||||||
using ReactiveUI;
|
using System.Text.RegularExpressions;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SPTInstaller.Models;
|
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));
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action<string, int> updateCallback = null)
|
|
||||||
{
|
{
|
||||||
try
|
Log.Error(ex, "Error while creating directories");
|
||||||
{
|
return Result.FromError(ex.Message);
|
||||||
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\\(?<NAME>[^\\]+)");
|
|
||||||
|
|
||||||
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<double> progress = null) =>
|
|
||||||
CopyDirectoryWithProgress(sourceDir, targetDir, (msg, prog) => progress?.Report(prog));
|
|
||||||
|
|
||||||
public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action<string, int> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action<string, int> 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\\(?<NAME>[^\\]+)");
|
||||||
|
|
||||||
|
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<double> progress = null) =>
|
||||||
|
CopyDirectoryWithProgress(sourceDir, targetDir, (msg, prog) => progress?.Report(prog));
|
||||||
|
|
||||||
|
public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action<string, int> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,57 +1,54 @@
|
|||||||
using System;
|
using System.Net.Http;
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace HttpClientProgress
|
namespace SPTInstaller.Helpers;
|
||||||
{
|
|
||||||
public static class HttpClientProgressExtensions
|
|
||||||
{
|
|
||||||
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<double> 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<long>(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
|
||||||
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<double> 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<long>(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<long> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
|
||||||
{
|
}
|
||||||
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];
|
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||||
long totalBytesRead = 0;
|
{
|
||||||
int bytesRead;
|
if (bufferSize < 0)
|
||||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||||
{
|
if (source is null)
|
||||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
throw new ArgumentNullException(nameof(source));
|
||||||
totalBytesRead += bytesRead;
|
if (!source.CanRead)
|
||||||
progress?.Report(totalBytesRead);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,40 +1,37 @@
|
|||||||
using Microsoft.Win32;
|
using System.Diagnostics;
|
||||||
using SPTInstaller.Models;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
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
|
string version = FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2];
|
||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
return Result.FromSuccess(version);
|
||||||
return null;
|
|
||||||
|
|
||||||
var uninstallStringValue = Registry.LocalMachine.OpenSubKey(registryInstall, false)
|
|
||||||
?.GetValue("UninstallString");
|
|
||||||
var info = (uninstallStringValue is string key) ? new FileInfo(key) : null;
|
|
||||||
|
|
||||||
return info?.DirectoryName;
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public static Result DetectOriginalGameVersion(string gamePath)
|
|
||||||
{
|
{
|
||||||
try
|
return Result.FromError($"File not found: {ex.Message}");
|
||||||
{
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,61 +1,60 @@
|
|||||||
using SPTInstaller.Models;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics;
|
using SPTInstaller.Models;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace SPTInstaller.Aki.Helper
|
namespace SPTInstaller.Helpers;
|
||||||
|
|
||||||
|
public enum PatcherExitCode
|
||||||
{
|
{
|
||||||
public enum PatcherExitCode
|
ProgramClosed = 0,
|
||||||
{
|
Success = 10,
|
||||||
ProgramClosed = 0,
|
EftExeNotFound = 11,
|
||||||
Success = 10,
|
NoPatchFolder = 12,
|
||||||
EftExeNotFound = 11,
|
MissingFile = 13,
|
||||||
NoPatchFolder = 12,
|
MissingDir = 14,
|
||||||
MissingFile = 13,
|
PatchFailed = 15
|
||||||
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();
|
var process = new Process();
|
||||||
process.StartInfo.FileName = executable.FullName;
|
process.StartInfo.FileName = executable.FullName;
|
||||||
process.StartInfo.WorkingDirectory = workingDir.FullName;
|
process.StartInfo.WorkingDirectory = workingDir.FullName;
|
||||||
process.EnableRaisingEvents = true;
|
process.EnableRaisingEvents = true;
|
||||||
process.StartInfo.Arguments = "autoclose";
|
process.StartInfo.Arguments = "autoclose";
|
||||||
process.Start();
|
process.Start();
|
||||||
|
|
||||||
process.WaitForExit();
|
process.WaitForExit();
|
||||||
|
|
||||||
switch ((PatcherExitCode)process.ExitCode)
|
switch ((PatcherExitCode)process.ExitCode)
|
||||||
{
|
{
|
||||||
case PatcherExitCode.Success:
|
case PatcherExitCode.Success:
|
||||||
return Result.FromSuccess("Patcher Finished Successfully, extracting Aki");
|
return Result.FromSuccess("Patcher Finished Successfully, extracting Aki");
|
||||||
|
|
||||||
case PatcherExitCode.ProgramClosed:
|
case PatcherExitCode.ProgramClosed:
|
||||||
return Result.FromError("Patcher was closed before completing!");
|
return Result.FromError("Patcher was closed before completing!");
|
||||||
|
|
||||||
case PatcherExitCode.EftExeNotFound:
|
case PatcherExitCode.EftExeNotFound:
|
||||||
return Result.FromError("EscapeFromTarkov.exe is missing from the install Path");
|
return Result.FromError("EscapeFromTarkov.exe is missing from the install Path");
|
||||||
|
|
||||||
case PatcherExitCode.NoPatchFolder:
|
case PatcherExitCode.NoPatchFolder:
|
||||||
return Result.FromError("Patchers Folder called 'Aki_Patches' is missing");
|
return Result.FromError("Patchers Folder called 'Aki_Patches' is missing");
|
||||||
|
|
||||||
case PatcherExitCode.MissingFile:
|
case PatcherExitCode.MissingFile:
|
||||||
return Result.FromError("EFT files was missing a Vital file to continue");
|
return Result.FromError("EFT files was missing a Vital file to continue");
|
||||||
|
|
||||||
case PatcherExitCode.PatchFailed:
|
case PatcherExitCode.PatchFailed:
|
||||||
return Result.FromError("A patch failed to apply");
|
return Result.FromError("A patch failed to apply");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return Result.FromError("an unknown error occurred in the patcher");
|
return Result.FromError("an unknown error occurred in the patcher");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,129 +1,127 @@
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using Splat;
|
using Splat;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace SPTInstaller.Helpers
|
namespace SPTInstaller.Helpers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A helper class to handle simple service registration to Splat with constructor parameter injection
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Splat only recognizes the registered types and doesn't account for interfaces :(</remarks>
|
||||||
|
internal static class ServiceHelper
|
||||||
{
|
{
|
||||||
/// <summary>
|
private static bool TryRegisterInstance<T, T2>(object[] parameters = null)
|
||||||
/// A helper class to handle simple service registration to Splat with constructor parameter injection
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Splat only recognizes the registered types and doesn't account for interfaces :(</remarks>
|
|
||||||
internal static class ServiceHelper
|
|
||||||
{
|
{
|
||||||
private static bool TryRegisterInstance<T, T2>(object[] parameters = null)
|
var instance = Activator.CreateInstance(typeof(T2), parameters);
|
||||||
|
|
||||||
|
if (instance != null)
|
||||||
{
|
{
|
||||||
var instance = Activator.CreateInstance(typeof(T2), parameters);
|
Locator.CurrentMutable.RegisterConstant<T>((T)instance);
|
||||||
|
return true;
|
||||||
if (instance != null)
|
|
||||||
{
|
|
||||||
Locator.CurrentMutable.RegisterConstant<T>((T)instance);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return false;
|
||||||
/// Register a class as a service
|
}
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">class to register</typeparam>
|
|
||||||
public static void Register<T>() where T : class => Register<T, T>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register a class as a service by another type
|
/// Register a class as a service
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">type to register as</typeparam>
|
/// <typeparam name="T">class to register</typeparam>
|
||||||
/// <typeparam name="T2">class to register</typeparam>
|
public static void Register<T>() where T : class => Register<T, T>();
|
||||||
public static void Register<T, T2>() where T : class
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a class as a service by another type
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">type to register as</typeparam>
|
||||||
|
/// <typeparam name="T2">class to register</typeparam>
|
||||||
|
public static void Register<T, T2>() 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<T, T2>()) return;
|
||||||
|
|
||||||
if(parmesan.Length == 0)
|
continue;
|
||||||
{
|
}
|
||||||
if (TryRegisterInstance<T, T2>()) return;
|
|
||||||
|
|
||||||
continue;
|
List<object> parameters = new List<object>();
|
||||||
}
|
|
||||||
|
|
||||||
List<object> parameters = new List<object>();
|
for(int i = 0; i < parmesan.Length; i++)
|
||||||
|
{
|
||||||
for(int i = 0; i < parmesan.Length; i++)
|
var parm = parmesan[i];
|
||||||
{
|
|
||||||
var parm = parmesan[i];
|
|
||||||
|
|
||||||
var parmValue = Get(parm.ParameterType);
|
var parmValue = Get(parm.ParameterType);
|
||||||
|
|
||||||
if (parmValue != null) parameters.Add(parmValue);
|
if (parmValue != null) parameters.Add(parmValue);
|
||||||
}
|
|
||||||
|
|
||||||
if (TryRegisterInstance<T, T2>(parameters.ToArray())) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a service from splat
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <exception cref="InvalidOperationException">Thrown if the service isn't found</exception>
|
|
||||||
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;
|
if (TryRegisterInstance<T, T2>(parameters.ToArray())) return;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a service from splat
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <exception cref="InvalidOperationException">Thrown if the service isn't found</exception>
|
|
||||||
public static T Get<T>()
|
|
||||||
{
|
|
||||||
var service = Locator.Current.GetService<T>();
|
|
||||||
|
|
||||||
if (service == null)
|
|
||||||
{
|
|
||||||
var message = $"Could not locate service of type '{nameof(T)}'";
|
|
||||||
Log.Error(message);
|
|
||||||
throw new InvalidOperationException(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all services of a type
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <exception cref="InvalidOperationException">thrown if no services are found</exception>
|
|
||||||
public static T[] GetAll<T>()
|
|
||||||
{
|
|
||||||
var services = Locator.Current.GetServices<T>().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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a service from splat
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown if the service isn't found</exception>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a service from splat
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown if the service isn't found</exception>
|
||||||
|
public static T Get<T>()
|
||||||
|
{
|
||||||
|
var service = Locator.Current.GetService<T>();
|
||||||
|
|
||||||
|
if (service == null)
|
||||||
|
{
|
||||||
|
var message = $"Could not locate service of type '{nameof(T)}'";
|
||||||
|
Log.Error(message);
|
||||||
|
throw new InvalidOperationException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all services of a type
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="InvalidOperationException">thrown if no services are found</exception>
|
||||||
|
public static T[] GetAll<T>()
|
||||||
|
{
|
||||||
|
var services = Locator.Current.GetServices<T>().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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,56 +1,53 @@
|
|||||||
using SharpCompress.Archives;
|
using System.Linq;
|
||||||
|
using SharpCompress.Archives;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SPTInstaller.Models;
|
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<double> progress = null)
|
||||||
{
|
{
|
||||||
public static Result Decompress(FileInfo ArchivePath, DirectoryInfo OutputFolderPath, IProgress<double> 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();
|
entry.WriteToDirectory(OutputFolderPath.FullName, new ExtractionOptions()
|
||||||
|
|
||||||
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()
|
ExtractFullPath = true,
|
||||||
{
|
Overwrite = true
|
||||||
ExtractFullPath = true,
|
});
|
||||||
Overwrite = true
|
|
||||||
});
|
|
||||||
|
|
||||||
processedEntries++;
|
processedEntries++;
|
||||||
|
|
||||||
if (progress != null)
|
if (progress != null)
|
||||||
{
|
|
||||||
progress.Report(Math.Floor(((double)processedEntries / totalEntries.Count()) * 100));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputFolderPath.Refresh();
|
|
||||||
|
|
||||||
if (!OutputFolderPath.Exists)
|
|
||||||
{
|
{
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,30 +1,26 @@
|
|||||||
using Serilog;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Aki.Helper;
|
|
||||||
using SPTInstaller.Interfaces;
|
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
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;
|
_data = data;
|
||||||
|
|
||||||
public CopyClientTask(InternalData data) : base("Copy Client Files")
|
|
||||||
{
|
|
||||||
_data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IResult> 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); });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override async Task<IResult> 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); });
|
||||||
|
}
|
||||||
|
}
|
@ -1,126 +1,123 @@
|
|||||||
using CG.Web.MegaApiClient;
|
using CG.Web.MegaApiClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using SPTInstaller.Aki.Helper;
|
|
||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
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<IResult> BuildMirrorList()
|
||||||
|
{
|
||||||
|
var progress = new Progress<double>((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<IResult> BuildMirrorList()
|
var mirrorsList = JsonConvert.DeserializeObject<List<DownloadMirror>>(File.ReadAllText(file.FullName));
|
||||||
|
|
||||||
|
if (mirrorsList is List<DownloadMirror> mirrors)
|
||||||
{
|
{
|
||||||
var progress = new Progress<double>((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d));});
|
_data.PatcherReleaseMirrors = mirrors;
|
||||||
|
|
||||||
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<List<DownloadMirror>>(File.ReadAllText(file.FullName));
|
|
||||||
|
|
||||||
if (mirrorsList is List<DownloadMirror> mirrors)
|
|
||||||
{
|
|
||||||
_data.PatcherReleaseMirrors = mirrors;
|
|
||||||
|
|
||||||
return Result.FromSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.FromError("Failed to deserialize mirrors list");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IResult> DownloadPatcherFromMirrors(IProgress<double> 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<IResult> TaskOperation()
|
|
||||||
{
|
|
||||||
var progress = new Progress<double>((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();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.FromError("Failed to deserialize mirrors list");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IResult> DownloadPatcherFromMirrors(IProgress<double> 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<IResult> TaskOperation()
|
||||||
|
{
|
||||||
|
var progress = new Progress<double>((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();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,59 +1,58 @@
|
|||||||
using SPTInstaller.Aki.Helper;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Interfaces;
|
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System.Threading.Tasks;
|
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;
|
_data = data;
|
||||||
|
|
||||||
public InitializationTask(InternalData data) : base("Startup")
|
|
||||||
{
|
|
||||||
_data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IResult> 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}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override async Task<IResult> 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}");
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +1,56 @@
|
|||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
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<bool> 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");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public override async Task<bool> CheckOperation()
|
|
||||||
{
|
{
|
||||||
var minRequiredVersion = new Version("6.0.0");
|
// TODO: logging
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,46 +1,44 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
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<bool> 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;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public override async Task<bool> CheckOperation()
|
|
||||||
{
|
{
|
||||||
try
|
// TODO: log exceptions
|
||||||
{
|
|
||||||
var minRequiredVersion = new Version("4.7.2");
|
|
||||||
|
|
||||||
var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full");
|
return false;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,89 +1,87 @@
|
|||||||
using Gitea.Api;
|
using Gitea.Api;
|
||||||
using Gitea.Client;
|
using Gitea.Client;
|
||||||
using SPTInstaller.Aki.Helper;
|
|
||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
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<IResult> 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);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public override async Task<IResult> TaskOperation()
|
|
||||||
{
|
{
|
||||||
try
|
//request failed
|
||||||
{
|
return Result.FromError($"Request Failed:\n{ex.Message}");
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,88 +1,85 @@
|
|||||||
using SPTInstaller.Aki.Helper;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Interfaces;
|
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
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;
|
_data = data;
|
||||||
|
|
||||||
public SetupClientTask(InternalData data) : base("Setup Client")
|
|
||||||
{
|
|
||||||
_data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IResult> 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<double>((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!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public override async Task<IResult> 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<double>((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!");
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,33 @@
|
|||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
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<IResult> 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<IResult> 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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,16 @@
|
|||||||
using System.Threading.Tasks;
|
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 string Id { get; }
|
public bool IsRequired { 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<IResult> RunCheck();
|
public Task<IResult> RunCheck();
|
||||||
}
|
}
|
||||||
}
|
|
@ -1,24 +1,23 @@
|
|||||||
using System.Threading.Tasks;
|
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<IResult> RunAsync();
|
public Task<IResult> RunAsync();
|
||||||
}
|
}
|
||||||
}
|
|
@ -1,14 +1,7 @@
|
|||||||
using System;
|
namespace SPTInstaller.Interfaces;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,183 +1,178 @@
|
|||||||
using Avalonia.Threading;
|
using ReactiveUI;
|
||||||
using ReactiveUI;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Splat;
|
|
||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using System;
|
|
||||||
using System.Security;
|
|
||||||
using System.Threading.Tasks;
|
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;
|
get => _id;
|
||||||
public string Id
|
private set => this.RaiseAndSetIfChanged(ref _id, value);
|
||||||
{
|
}
|
||||||
get => _id;
|
|
||||||
private set => this.RaiseAndSetIfChanged(ref _id, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _name;
|
private string _name;
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get => _name;
|
get => _name;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _name, value);
|
private set => this.RaiseAndSetIfChanged(ref _name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isComleted;
|
private bool _isComleted;
|
||||||
public bool IsCompleted
|
public bool IsCompleted
|
||||||
{
|
{
|
||||||
get => _isComleted;
|
get => _isComleted;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _isComleted, value);
|
private set => this.RaiseAndSetIfChanged(ref _isComleted, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _hasErrors;
|
private bool _hasErrors;
|
||||||
public bool HasErrors
|
public bool HasErrors
|
||||||
{
|
{
|
||||||
get => _hasErrors;
|
get => _hasErrors;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _hasErrors, value);
|
private set => this.RaiseAndSetIfChanged(ref _hasErrors, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
public bool IsRunning
|
public bool IsRunning
|
||||||
{
|
{
|
||||||
get => _isRunning;
|
get => _isRunning;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _isRunning, value);
|
private set => this.RaiseAndSetIfChanged(ref _isRunning, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int _progress;
|
private int _progress;
|
||||||
public int Progress
|
public int Progress
|
||||||
{
|
{
|
||||||
get => _progress;
|
get => _progress;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _progress, value);
|
private set => this.RaiseAndSetIfChanged(ref _progress, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _showProgress;
|
private bool _showProgress;
|
||||||
public bool ShowProgress
|
public bool ShowProgress
|
||||||
{
|
{
|
||||||
get => _showProgress;
|
get => _showProgress;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _showProgress, value);
|
private set => this.RaiseAndSetIfChanged(ref _showProgress, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _indeterminateProgress;
|
private bool _indeterminateProgress;
|
||||||
public bool IndeterminateProgress
|
public bool IndeterminateProgress
|
||||||
{
|
{
|
||||||
get => _indeterminateProgress;
|
get => _indeterminateProgress;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _indeterminateProgress, value);
|
private set => this.RaiseAndSetIfChanged(ref _indeterminateProgress, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _statusMessage;
|
private string _statusMessage;
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
{
|
{
|
||||||
get => _statusMessage;
|
get => _statusMessage;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
|
private set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _statusDetails;
|
private string _statusDetails;
|
||||||
public string StatusDetails
|
public string StatusDetails
|
||||||
{
|
{
|
||||||
get => _statusDetails;
|
get => _statusDetails;
|
||||||
private set => this.RaiseAndSetIfChanged(ref _statusDetails, value);
|
private set => this.RaiseAndSetIfChanged(ref _statusDetails, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ProgressStyle
|
public enum ProgressStyle
|
||||||
{
|
{
|
||||||
Hidden = 0,
|
Hidden = 0,
|
||||||
Shown,
|
Shown,
|
||||||
Indeterminate,
|
Indeterminate,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update the status details of the task
|
/// Update the status details of the task
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The main message to display. Not updated if null</param>
|
/// <param name="message">The main message to display. Not updated if null</param>
|
||||||
/// <param name="details">The details of the task. Not updated if null</param>
|
/// <param name="details">The details of the task. Not updated if null</param>
|
||||||
/// <param name="progress">Progress of the task. Overrides progressStyle if a non-null value is supplied</param>
|
/// <param name="progress">Progress of the task. Overrides progressStyle if a non-null value is supplied</param>
|
||||||
/// <param name="progressStyle">The style of the progress bar</param>
|
/// <param name="progressStyle">The style of the progress bar</param>
|
||||||
public void SetStatus(string? message, string? details, int? progress = null, ProgressStyle? progressStyle = null, bool noLog = false)
|
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} <===>");
|
||||||
{
|
|
||||||
Log.Information($" <===> {message} <===>");
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusMessage = message;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(details != null && details != StatusDetails)
|
StatusMessage = message;
|
||||||
{
|
}
|
||||||
if (!noLog && !string.IsNullOrWhiteSpace(details))
|
|
||||||
{
|
|
||||||
Log.Information(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusDetails = details;
|
if(details != null && details != StatusDetails)
|
||||||
|
{
|
||||||
|
if (!noLog && !string.IsNullOrWhiteSpace(details))
|
||||||
|
{
|
||||||
|
Log.Information(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressStyle != null)
|
StatusDetails = details;
|
||||||
{
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress != null)
|
if (progressStyle != null)
|
||||||
|
{
|
||||||
|
switch (progressStyle)
|
||||||
{
|
{
|
||||||
ShowProgress = true;
|
case ProgressStyle.Hidden:
|
||||||
IndeterminateProgress = false;
|
ShowProgress = false;
|
||||||
Progress = progress.Value;
|
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;
|
ShowProgress = true;
|
||||||
Id = Guid.NewGuid().ToString();
|
IndeterminateProgress = false;
|
||||||
|
Progress = progress.Value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
public InstallerTaskBase(string name)
|
||||||
/// A method for the install controller to call. Do not use this within your task
|
{
|
||||||
/// </summary>
|
Name = name;
|
||||||
/// <returns></returns>
|
Id = Guid.NewGuid().ToString();
|
||||||
public async Task<IResult> RunAsync()
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A method for the install controller to call. Do not use this within your task
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IResult> RunAsync()
|
||||||
|
{
|
||||||
|
IsRunning = true;
|
||||||
|
|
||||||
|
var result = await TaskOperation();
|
||||||
|
|
||||||
|
IsRunning = false;
|
||||||
|
|
||||||
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
IsRunning = true;
|
HasErrors = true;
|
||||||
|
|
||||||
var result = await TaskOperation();
|
|
||||||
|
|
||||||
IsRunning = false;
|
|
||||||
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
HasErrors = true;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
IsCompleted = true;
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
IsCompleted = true;
|
||||||
/// The task you want to run
|
|
||||||
/// </summary>
|
return result;
|
||||||
/// <returns></returns>
|
|
||||||
public abstract Task<IResult> TaskOperation();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/// <summary>
|
||||||
|
/// The task you want to run
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract Task<IResult> TaskOperation();
|
||||||
|
}
|
@ -1,58 +1,56 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace SPTInstaller.Models
|
namespace SPTInstaller.Models;
|
||||||
|
|
||||||
|
public class InternalData
|
||||||
{
|
{
|
||||||
public class InternalData
|
/// <summary>
|
||||||
{
|
/// The folder to install SPT into
|
||||||
/// <summary>
|
/// </summary>
|
||||||
/// The folder to install SPT into
|
public string? TargetInstallPath { get; set; }
|
||||||
/// </summary>
|
|
||||||
public string? TargetInstallPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The orginal EFT game path
|
/// The orginal EFT game path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? OriginalGamePath { get; set; }
|
public string? OriginalGamePath { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The original EFT game version
|
/// The original EFT game version
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string OriginalGameVersion { get; set; }
|
public string OriginalGameVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Patcher zip file info
|
/// Patcher zip file info
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FileInfo PatcherZipInfo { get; set; }
|
public FileInfo PatcherZipInfo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SPT-AKI zip file info
|
/// SPT-AKI zip file info
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FileInfo AkiZipInfo { get; set; }
|
public FileInfo AkiZipInfo { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The release download link for SPT-AKI
|
/// The release download link for SPT-AKI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AkiReleaseDownloadLink { get; set; }
|
public string AkiReleaseDownloadLink { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The release zip hash
|
/// The release zip hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string AkiReleaseHash { get; set; } = null;
|
public string AkiReleaseHash { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The release download link for the patcher mirror list
|
/// The release download link for the patcher mirror list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PatcherMirrorsLink { get; set; }
|
public string PatcherMirrorsLink { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The release download mirrors for the patcher
|
/// The release download mirrors for the patcher
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<DownloadMirror> PatcherReleaseMirrors { get; set; } = null;
|
public List<DownloadMirror> PatcherReleaseMirrors { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether or not a patch is needed to downgrade the client files
|
/// Whether or not a patch is needed to downgrade the client files
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool PatchNeeded { get; set; }
|
public bool PatchNeeded { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
@ -1,76 +1,74 @@
|
|||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
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;
|
get => _id;
|
||||||
public string Id
|
set => this.RaiseAndSetIfChanged(ref _id, value);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base class for pre-checks to run before installation
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The display name of the pre-check</param>
|
|
||||||
/// <param name="required">If installation should stop on failing this pre-check</param>
|
|
||||||
public PreCheckBase(string name, bool required)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
IsRequired = required;
|
|
||||||
Id = Guid.NewGuid().ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IResult> RunCheck()
|
|
||||||
{
|
|
||||||
IsRunning = true;
|
|
||||||
Passed = await CheckOperation();
|
|
||||||
IsRunning = false;
|
|
||||||
IsPending = false;
|
|
||||||
|
|
||||||
return Passed ? Result.FromSuccess() : Result.FromError($"PreCheck Failed: {Name}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Task<bool> CheckOperation();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for pre-checks to run before installation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The display name of the pre-check</param>
|
||||||
|
/// <param name="required">If installation should stop on failing this pre-check</param>
|
||||||
|
public PreCheckBase(string name, bool required)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
IsRequired = required;
|
||||||
|
Id = Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> RunCheck()
|
||||||
|
{
|
||||||
|
IsRunning = true;
|
||||||
|
Passed = await CheckOperation();
|
||||||
|
IsRunning = false;
|
||||||
|
IsPending = false;
|
||||||
|
|
||||||
|
return Passed ? Result.FromSuccess() : Result.FromError($"PreCheck Failed: {Name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<bool> CheckOperation();
|
||||||
|
}
|
@ -1,20 +1,19 @@
|
|||||||
using SPTInstaller.Interfaces;
|
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; }
|
Message = message;
|
||||||
|
Succeeded = succeeded;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static Result FromSuccess(string message = "") => new(message, true);
|
||||||
|
public static Result FromError(string message) => new(message, false);
|
||||||
|
}
|
@ -12,44 +12,44 @@ using SPTInstaller.Models;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
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
|
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
|
||||||
// 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.
|
// Register all the things
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
// Regestering as base classes so ReactiveUI works correctly. Doesn't seem to like the interfaces :(
|
||||||
{
|
ServiceHelper.Register<InternalData>();
|
||||||
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
|
ServiceHelper.Register<PreCheckBase, NetFramework472PreCheck>();
|
||||||
|
ServiceHelper.Register<PreCheckBase, NetCore6PreCheck>();
|
||||||
// Register all the things
|
ServiceHelper.Register<PreCheckBase, FreeSpacePreCheck>();
|
||||||
// Regestering as base classes so ReactiveUI works correctly. Doesn't seem to like the interfaces :(
|
|
||||||
ServiceHelper.Register<InternalData>();
|
|
||||||
ServiceHelper.Register<PreCheckBase, NetFramework472PreCheck>();
|
|
||||||
ServiceHelper.Register<PreCheckBase, NetCore6PreCheck>();
|
|
||||||
ServiceHelper.Register<PreCheckBase, FreeSpacePreCheck>();
|
|
||||||
#if !TEST
|
#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()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.Debug()
|
.MinimumLevel.Debug()
|
||||||
.WriteTo
|
.WriteTo
|
||||||
.File(path: logPath,
|
.File(path: logPath,
|
||||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug,
|
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug,
|
||||||
rollingInterval: RollingInterval.Day)
|
rollingInterval: RollingInterval.Day)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
ServiceHelper.Register<InstallerTaskBase, InitializationTask>();
|
ServiceHelper.Register<InstallerTaskBase, InitializationTask>();
|
||||||
ServiceHelper.Register<InstallerTaskBase, ReleaseCheckTask>();
|
ServiceHelper.Register<InstallerTaskBase, ReleaseCheckTask>();
|
||||||
ServiceHelper.Register<InstallerTaskBase, DownloadTask>();
|
ServiceHelper.Register<InstallerTaskBase, DownloadTask>();
|
||||||
ServiceHelper.Register<InstallerTaskBase, CopyClientTask>();
|
ServiceHelper.Register<InstallerTaskBase, CopyClientTask>();
|
||||||
ServiceHelper.Register<InstallerTaskBase, SetupClientTask>();
|
ServiceHelper.Register<InstallerTaskBase, SetupClientTask>();
|
||||||
#else
|
#else
|
||||||
for (int i = 0; i < 5; i++)
|
for (int i = 0; i < 5; i++)
|
||||||
{
|
{
|
||||||
@ -57,19 +57,18 @@ namespace SPTInstaller
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
// 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<InstallerTaskBase>().ToArray() as IProgressableTask[];
|
var tasks = Locator.Current.GetServices<InstallerTaskBase>().ToArray() as IProgressableTask[];
|
||||||
var preChecks = Locator.Current.GetServices<PreCheckBase>().ToArray() as IPreCheck[];
|
var preChecks = Locator.Current.GetServices<PreCheckBase>().ToArray() as IPreCheck[];
|
||||||
|
|
||||||
var installer = new InstallController(tasks, preChecks);
|
var installer = new InstallController(tasks, preChecks);
|
||||||
|
|
||||||
// manually register install controller
|
// manually register install controller
|
||||||
Locator.CurrentMutable.RegisterConstant(installer);
|
Locator.CurrentMutable.RegisterConstant(installer);
|
||||||
|
|
||||||
return AppBuilder.Configure<App>()
|
return AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
.LogToTrace()
|
.LogToTrace()
|
||||||
.UseReactiveUI();
|
.UseReactiveUI();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,28 +1,26 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using SPTInstaller.ViewModels;
|
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");
|
return (Control)Activator.CreateInstance(type)!;
|
||||||
var type = Type.GetType(name);
|
|
||||||
|
|
||||||
if (type != null)
|
|
||||||
{
|
|
||||||
return (Control)Activator.CreateInstance(type)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new TextBlock { Text = "Not Found: " + name };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Match(object data)
|
return new TextBlock { Text = "Not Found: " + name };
|
||||||
{
|
}
|
||||||
return data is ViewModelBase;
|
|
||||||
}
|
public bool Match(object data)
|
||||||
|
{
|
||||||
|
return data is ViewModelBase;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,35 +4,32 @@ using SPTInstaller.Helpers;
|
|||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
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;
|
get => _currentTask;
|
||||||
public IProgressableTask CurrentTask
|
set => this.RaiseAndSetIfChanged(ref _currentTask, value);
|
||||||
{
|
|
||||||
get => _currentTask;
|
|
||||||
set => this.RaiseAndSetIfChanged(ref _currentTask, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<InstallerTaskBase> MyTasks { get; set; }
|
|
||||||
= new ObservableCollection<InstallerTaskBase>(ServiceHelper.GetAll<InstallerTaskBase>());
|
|
||||||
|
|
||||||
public InstallViewModel(IScreen host) : base(host)
|
|
||||||
{
|
|
||||||
var installer = ServiceHelper.Get<InstallController>();
|
|
||||||
|
|
||||||
installer.TaskChanged += (sender, task) => CurrentTask = task;
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var result = await installer.RunTasks();
|
|
||||||
|
|
||||||
NavigateTo(new MessageViewModel(HostScreen, result));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public ObservableCollection<InstallerTaskBase> MyTasks { get; set; } = new(ServiceHelper.GetAll<InstallerTaskBase>());
|
||||||
|
|
||||||
|
public InstallViewModel(IScreen host) : base(host)
|
||||||
|
{
|
||||||
|
var installer = ServiceHelper.Get<InstallController>();
|
||||||
|
|
||||||
|
installer.TaskChanged += (sender, task) => CurrentTask = task;
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var result = await installer.RunTasks();
|
||||||
|
|
||||||
|
NavigateTo(new MessageViewModel(HostScreen, result));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,50 +1,48 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System;
|
|
||||||
using System.Reflection;
|
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();
|
get => _title;
|
||||||
public ViewModelActivator Activator { get; } = new ViewModelActivator();
|
set => this.RaiseAndSetIfChanged(ref _title, value);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -4,44 +4,43 @@ using Serilog;
|
|||||||
using SPTInstaller.Interfaces;
|
using SPTInstaller.Interfaces;
|
||||||
using System.Windows.Input;
|
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;
|
get => _HasErrors;
|
||||||
public bool HasErrors
|
set => this.RaiseAndSetIfChanged(ref _HasErrors, value);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +1,58 @@
|
|||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System.Threading.Tasks;
|
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; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delay the return of the viewmodel
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Milliseconds">The amount of time in milliseconds to delay</param>
|
||||||
|
/// <returns>The viewmodel after the delay time</returns>
|
||||||
|
/// <remarks>Useful to delay the navigation to another view. For instance, to allow an animation to complete.</remarks>
|
||||||
|
private async Task<ViewModelBase> WithDelay(int Milliseconds)
|
||||||
{
|
{
|
||||||
public ViewModelActivator Activator { get; } = new ViewModelActivator();
|
await Task.Delay(Milliseconds);
|
||||||
|
|
||||||
public string? UrlPathSegment => Guid.NewGuid().ToString().Substring(0, 7);
|
return this;
|
||||||
|
|
||||||
public IScreen HostScreen { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delay the return of the viewmodel
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Milliseconds">The amount of time in milliseconds to delay</param>
|
|
||||||
/// <returns>The viewmodel after the delay time</returns>
|
|
||||||
/// <remarks>Useful to delay the navigation to another view. For instance, to allow an animation to complete.</remarks>
|
|
||||||
private async Task<ViewModelBase> WithDelay(int Milliseconds)
|
|
||||||
{
|
|
||||||
await Task.Delay(Milliseconds);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigate to another viewmodel after a delay
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ViewModel"></param>
|
|
||||||
/// <param name="Milliseconds"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task NavigateToWithDelay(ViewModelBase ViewModel, int Milliseconds)
|
|
||||||
{
|
|
||||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
HostScreen.Router.Navigate.Execute(await ViewModel.WithDelay(Milliseconds));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigate to another viewmodel
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ViewModel"></param>
|
|
||||||
public void NavigateTo(ViewModelBase ViewModel)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
|
||||||
{
|
|
||||||
HostScreen.Router.Navigate.Execute(ViewModel);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ViewModelBase(IScreen Host)
|
|
||||||
{
|
|
||||||
HostScreen = Host;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigate to another viewmodel after a delay
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ViewModel"></param>
|
||||||
|
/// <param name="Milliseconds"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task NavigateToWithDelay(ViewModelBase ViewModel, int Milliseconds)
|
||||||
|
{
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
HostScreen.Router.Navigate.Execute(await ViewModel.WithDelay(Milliseconds));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Navigate to another viewmodel
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ViewModel"></param>
|
||||||
|
public void NavigateTo(ViewModelBase ViewModel)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
HostScreen.Router.Navigate.Execute(ViewModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewModelBase(IScreen Host)
|
||||||
|
{
|
||||||
|
HostScreen = Host;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using SPTInstaller.ViewModels;
|
using SPTInstaller.ViewModels;
|
||||||
|
|
||||||
namespace SPTInstaller.Views
|
namespace SPTInstaller.Views;
|
||||||
|
|
||||||
|
public partial class InstallView : ReactiveUserControl<InstallViewModel>
|
||||||
{
|
{
|
||||||
public partial class InstallView : ReactiveUserControl<InstallViewModel>
|
public InstallView()
|
||||||
{
|
{
|
||||||
public InstallView()
|
InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,11 @@
|
|||||||
using Avalonia.Controls;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,12 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using SPTInstaller.ViewModels;
|
using SPTInstaller.ViewModels;
|
||||||
|
|
||||||
namespace SPTInstaller.Views
|
namespace SPTInstaller.Views;
|
||||||
|
|
||||||
|
public partial class MessageView : ReactiveUserControl<MessageViewModel>
|
||||||
{
|
{
|
||||||
public partial class MessageView : ReactiveUserControl<MessageViewModel>
|
public MessageView()
|
||||||
{
|
{
|
||||||
public MessageView()
|
InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,12 @@
|
|||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
using SPTInstaller.ViewModels;
|
using SPTInstaller.ViewModels;
|
||||||
|
|
||||||
namespace SPTInstaller.Views
|
namespace SPTInstaller.Views;
|
||||||
|
|
||||||
|
public partial class PreChecksView : ReactiveUserControl<PreChecksViewModel>
|
||||||
{
|
{
|
||||||
public partial class PreChecksView : ReactiveUserControl<PreChecksViewModel>
|
public PreChecksView()
|
||||||
{
|
{
|
||||||
public PreChecksView()
|
InitializeComponent();
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user