Merge pull request 'Refactoring, namespace-imports and small improvements' (#18) from Schrader/SPT-AKI-Installer:refactoring/namespace-imports into master
Reviewed-on: CWX/SPT-AKI-Installer#18
This commit is contained in:
commit
886770253a
57
.editorconfig
Normal file
57
.editorconfig
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8-bom
|
||||||
|
end_of_line = crlf
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = false
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# Microsoft .NET properties
|
||||||
|
csharp_style_namespace_declarations=file_scoped:error
|
||||||
|
csharp_new_line_before_members_in_object_initializers = false
|
||||||
|
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:warning
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||||
|
csharp_style_var_elsewhere = true:suggestion
|
||||||
|
csharp_style_var_for_built_in_types = true:warning
|
||||||
|
csharp_style_var_when_type_is_apparent = true:warning
|
||||||
|
dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||||
|
dotnet_style_qualification_for_event = false:suggestion
|
||||||
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
|
dotnet_style_qualification_for_method = false:suggestion
|
||||||
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||||
|
|
||||||
|
# ReSharper properties
|
||||||
|
resharper_autodetect_indent_settings = true
|
||||||
|
resharper_enforce_line_ending_style = true
|
||||||
|
resharper_formatter_off_tag = @formatter:off
|
||||||
|
resharper_formatter_on_tag = @formatter:on
|
||||||
|
resharper_formatter_tags_enabled = true
|
||||||
|
resharper_use_indent_from_vs = false
|
||||||
|
resharper_wrap_lines = true
|
||||||
|
|
||||||
|
# ReSharper inspection severities
|
||||||
|
resharper_arrange_redundant_parentheses_highlighting = hint
|
||||||
|
resharper_arrange_this_qualifier_highlighting = hint
|
||||||
|
resharper_arrange_type_member_modifiers_highlighting = hint
|
||||||
|
resharper_arrange_type_modifiers_highlighting = hint
|
||||||
|
resharper_built_in_type_reference_style_for_member_access_highlighting = hint
|
||||||
|
resharper_built_in_type_reference_style_highlighting = hint
|
||||||
|
resharper_redundant_base_qualifier_highlighting = warning
|
||||||
|
resharper_suggest_var_or_type_built_in_types_highlighting = hint
|
||||||
|
resharper_suggest_var_or_type_elsewhere_highlighting = hint
|
||||||
|
resharper_suggest_var_or_type_simple_types_highlighting = hint
|
||||||
|
resharper_web_config_module_not_resolved_highlighting = warning
|
||||||
|
resharper_web_config_type_not_resolved_highlighting = warning
|
||||||
|
resharper_web_config_wrong_module_highlighting = warning
|
||||||
|
|
||||||
|
[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cppm,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
@ -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)
|
public static bool GetSpan(AvaloniaObject element)
|
||||||
{
|
{
|
||||||
element.SetValue(SpanProperty, value);
|
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; }
|
public async Task<IResult> RunPreChecks()
|
||||||
private IProgressableTask[] _tasks { get; set; }
|
{
|
||||||
|
Log.Information("-<>--<>- Running PreChecks -<>--<>-");
|
||||||
|
var requiredResults = new List<IResult>();
|
||||||
|
|
||||||
public InstallController(IProgressableTask[] tasks, IPreCheck[] preChecks = null)
|
_preChecks.ForEach(x => x.IsPending = true);
|
||||||
|
|
||||||
|
foreach (var check in _preChecks)
|
||||||
{
|
{
|
||||||
_tasks = tasks;
|
var result = await check.RunCheck();
|
||||||
_preChecks = preChecks;
|
|
||||||
|
Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}");
|
||||||
|
|
||||||
|
if (check.IsRequired)
|
||||||
|
{
|
||||||
|
requiredResults.Add(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IResult> RunPreChecks()
|
if (requiredResults.Any(result => !result.Succeeded))
|
||||||
{
|
{
|
||||||
Log.Information("-<>--<>- Running PreChecks -<>--<>-");
|
return Result.FromError("Some required checks have failed");
|
||||||
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()
|
return Result.FromSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> RunTasks()
|
||||||
|
{
|
||||||
|
Log.Information("-<>--<>- Running Installer Tasks -<>--<>-");
|
||||||
|
|
||||||
|
foreach (var task in _tasks)
|
||||||
{
|
{
|
||||||
Log.Information("-<>--<>- Running Installer Tasks -<>--<>-");
|
TaskChanged?.Invoke(null, task);
|
||||||
|
|
||||||
foreach (var task in _tasks)
|
var result = await task.RunAsync();
|
||||||
{
|
|
||||||
TaskChanged?.Invoke(null, task);
|
|
||||||
|
|
||||||
var result = await task.RunAsync();
|
if (!result.Succeeded) return result;
|
||||||
|
|
||||||
if (!result.Succeeded) return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.FromSuccess("Install Complete. Happy Playing!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.FromSuccess("Install Complete. Happy Playing!");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,29 +1,27 @@
|
|||||||
using Avalonia.Data.Converters;
|
using System.Globalization;
|
||||||
using System;
|
using Avalonia.Data.Converters;
|
||||||
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()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<string> TitleProperty =
|
public static readonly StyledProperty<string> TitleProperty =
|
||||||
AvaloniaProperty.Register<TitleBar, string>(nameof(Title));
|
AvaloniaProperty.Register<TitleBar, string>(nameof(Title));
|
||||||
|
|
||||||
public string Title
|
public string Title
|
||||||
{
|
{
|
||||||
get => GetValue(TitleProperty);
|
get => GetValue(TitleProperty);
|
||||||
set => SetValue(TitleProperty, value);
|
set => SetValue(TitleProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> ButtonForegroundProperty =
|
public static readonly StyledProperty<IBrush> ButtonForegroundProperty =
|
||||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(ButtonForeground));
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(ButtonForeground));
|
||||||
|
|
||||||
public IBrush ButtonForeground
|
public IBrush ButtonForeground
|
||||||
{
|
{
|
||||||
get => GetValue(ButtonForegroundProperty);
|
get => GetValue(ButtonForegroundProperty);
|
||||||
set => SetValue(ButtonForegroundProperty, value);
|
set => SetValue(ButtonForegroundProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static new readonly StyledProperty<IBrush> ForegroundProperty =
|
public static new readonly StyledProperty<IBrush> ForegroundProperty =
|
||||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Foreground));
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Foreground));
|
||||||
|
|
||||||
public new IBrush Foreground
|
public new IBrush Foreground
|
||||||
{
|
{
|
||||||
get => GetValue(ForegroundProperty);
|
get => GetValue(ForegroundProperty);
|
||||||
set => SetValue(ForegroundProperty, value);
|
set => SetValue(ForegroundProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static new readonly StyledProperty<IBrush> BackgroundProperty =
|
public static new readonly StyledProperty<IBrush> BackgroundProperty =
|
||||||
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Background));
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Background));
|
||||||
|
|
||||||
public new IBrush Background
|
public new IBrush Background
|
||||||
{
|
{
|
||||||
get => GetValue(BackgroundProperty);
|
get => GetValue(BackgroundProperty);
|
||||||
set => SetValue(BackgroundProperty, value);
|
set => SetValue(BackgroundProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Close Button Command (X Button) Property
|
//Close Button Command (X Button) Property
|
||||||
public static readonly StyledProperty<ICommand> XButtonCommandProperty =
|
public static readonly StyledProperty<ICommand> XButtonCommandProperty =
|
||||||
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(XButtonCommand));
|
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(XButtonCommand));
|
||||||
|
|
||||||
public ICommand XButtonCommand
|
public ICommand XButtonCommand
|
||||||
{
|
{
|
||||||
get => GetValue(XButtonCommandProperty);
|
get => GetValue(XButtonCommandProperty);
|
||||||
set => SetValue(XButtonCommandProperty, value);
|
set => SetValue(XButtonCommandProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Minimize Button Command (- Button) Property
|
//Minimize Button Command (- Button) Property
|
||||||
public static readonly StyledProperty<ICommand> MinButtonCommandProperty =
|
public static readonly StyledProperty<ICommand> MinButtonCommandProperty =
|
||||||
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(MinButtonCommand));
|
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(MinButtonCommand));
|
||||||
|
|
||||||
public ICommand MinButtonCommand
|
public ICommand MinButtonCommand
|
||||||
{
|
{
|
||||||
get => GetValue(MinButtonCommandProperty);
|
get => GetValue(MinButtonCommandProperty);
|
||||||
set => SetValue(MinButtonCommandProperty, value);
|
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)
|
private static async Task<Result> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
|
return Result.FromError($"Failed to download {outputFile.Name}");
|
||||||
|
|
||||||
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)
|
|
||||||
|
if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash))
|
||||||
{
|
{
|
||||||
return Result.FromError(ex.Message);
|
return Result.FromError("Hash mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
private static async Task<Result> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
|
||||||
{
|
{
|
||||||
try
|
return Result.FromError(ex.Message);
|
||||||
{
|
|
||||||
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)
|
private static async Task<Result> ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
|
||||||
|
|
||||||
try
|
using var patcherFileStream = cacheFile.Open(FileMode.Create);
|
||||||
{
|
{
|
||||||
var result = await ProcessInboundFileAsync(cacheFile, targetLink, progress, expectedHash);
|
await downloadStream.CopyToAsync(patcherFileStream);
|
||||||
|
}
|
||||||
|
|
||||||
return result.Succeeded ? cacheFile : null;
|
patcherFileStream.Close();
|
||||||
}
|
|
||||||
catch(Exception ex)
|
if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash))
|
||||||
{
|
{
|
||||||
Log.Error(ex, $"Error while getting file: {fileName}");
|
return Result.FromError("Hash mismatch");
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
|
catch(Exception ex)
|
||||||
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null)
|
|
||||||
{
|
{
|
||||||
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
return Result.FromError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
private static async Task<Result> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
{
|
{
|
||||||
var result = await ProcessInboundStreamAsync(cacheFile, fileDownloadStream, expectedHash);
|
try
|
||||||
|
{
|
||||||
|
if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
|
||||||
|
|
||||||
return result.Succeeded ? cacheFile : null;
|
return await DownloadFile(cacheFile, targetLink, progress, expectedHash);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, $"Error while getting file: {fileName}");
|
return Result.FromError(ex.Message);
|
||||||
return null;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
public static bool CheckHash(FileInfo file, string expectedHash)
|
||||||
|
{
|
||||||
|
using var md5Service = MD5.Create();
|
||||||
|
using var sourceStream = file.OpenRead();
|
||||||
|
|
||||||
return matched;
|
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)
|
return Result.FromSuccess();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error while creating directories");
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Error while creating directories");
|
updateCallback?.Invoke(file.Name, (int)Math.Floor(((double)processedFiles / totalFiles) * 100));
|
||||||
return Result.FromError(ex.Message);
|
|
||||||
|
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-");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action<string, int> updateCallback = null)
|
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
|
||||||
{
|
{
|
||||||
try
|
var iterateDirectoriesResult = IterateDirectories(sourceDir, targetDir);
|
||||||
{
|
|
||||||
int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length;
|
|
||||||
int processedFiles = 0;
|
|
||||||
|
|
||||||
foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories))
|
if(!iterateDirectoriesResult.Succeeded) return iterateDirectoriesResult;
|
||||||
{
|
|
||||||
updateCallback?.Invoke(file.Name, (int)Math.Floor(((double)processedFiles / totalFiles) * 100));
|
|
||||||
|
|
||||||
File.Copy(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName), true);
|
var iterateFilesResult = IterateFiles(sourceDir, targetDir, updateCallback);
|
||||||
processedFiles++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.FromSuccess();
|
if (!iterateFilesResult.Succeeded) return iterateDirectoriesResult;
|
||||||
}
|
|
||||||
catch (Exception ex)
|
return Result.FromSuccess();
|
||||||
{
|
|
||||||
Log.Error(ex, "Error while copying files");
|
|
||||||
return Result.FromError(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public static string GetRedactedPath(string path)
|
|
||||||
{
|
{
|
||||||
var nameMatched = Regex.Match(path, @".:\\[uU]sers\\(?<NAME>[^\\]+)");
|
Log.Error(ex, "Error during directory copy");
|
||||||
|
return Result.FromError(ex.Message);
|
||||||
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>();
|
|
||||||
|
|
||||||
for(int i = 0; i < parmesan.Length; i++)
|
|
||||||
{
|
|
||||||
var parm = parmesan[i];
|
|
||||||
|
|
||||||
var parmValue = Get(parm.ParameterType);
|
|
||||||
|
|
||||||
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;
|
List<object> parameters = new List<object>();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
for(int i = 0; i < parmesan.Length; i++)
|
||||||
/// 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)}'";
|
var parm = parmesan[i];
|
||||||
Log.Error(message);
|
|
||||||
throw new InvalidOperationException(message);
|
var parmValue = Get(parm.ParameterType);
|
||||||
|
|
||||||
|
if (parmValue != null) parameters.Add(parmValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return service;
|
if (TryRegisterInstance<T, T2>(parameters.ToArray())) return;
|
||||||
}
|
|
||||||
|
|
||||||
/// <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")
|
public override async Task<IResult> TaskOperation()
|
||||||
{
|
{
|
||||||
_data = data;
|
SetStatus("Copying Client Files", "", 0);
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IResult> TaskOperation()
|
var originalGameDirInfo = new DirectoryInfo(_data.OriginalGamePath);
|
||||||
{
|
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
||||||
SetStatus("Copying Client Files", "", 0);
|
|
||||||
|
|
||||||
var originalGameDirInfo = new DirectoryInfo(_data.OriginalGamePath);
|
return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo, (message, progress) => { SetStatus(null, message, progress, null, true); });
|
||||||
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")
|
public override async Task<IResult> TaskOperation()
|
||||||
|
{
|
||||||
|
SetStatus("Initializing", $"Target Install Path: {FileHelper.GetRedactedPath(_data.TargetInstallPath)}");
|
||||||
|
|
||||||
|
_data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
||||||
|
|
||||||
|
if (_data.OriginalGamePath == null)
|
||||||
{
|
{
|
||||||
_data = data;
|
return Result.FromError("EFT IS NOT INSTALLED!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<IResult> TaskOperation()
|
SetStatus(null, $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}");
|
||||||
|
|
||||||
|
var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath);
|
||||||
|
|
||||||
|
if (!result.Succeeded)
|
||||||
{
|
{
|
||||||
SetStatus("Initializing", $"Target Install Path: {FileHelper.GetRedactedPath(_data.TargetInstallPath)}");
|
return result;
|
||||||
|
|
||||||
_data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
|
||||||
|
|
||||||
if (_data.OriginalGamePath == null)
|
|
||||||
{
|
|
||||||
return Result.FromError("EFT IS NOT INSTALLED!");
|
|
||||||
}
|
|
||||||
|
|
||||||
SetStatus(null, $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}");
|
|
||||||
|
|
||||||
var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath);
|
|
||||||
|
|
||||||
if (!result.Succeeded)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_data.OriginalGameVersion = result.Message;
|
|
||||||
|
|
||||||
SetStatus(null, $"Installed EFT Game Version: {_data.OriginalGameVersion}");
|
|
||||||
|
|
||||||
if (_data.OriginalGamePath == null)
|
|
||||||
{
|
|
||||||
return Result.FromError("Unable to find original EFT directory, please make sure EFT is installed. Please also run EFT once");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_data.OriginalGamePath == _data.TargetInstallPath)
|
|
||||||
{
|
|
||||||
return Result.FromError("Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe")))
|
|
||||||
{
|
|
||||||
return Result.FromError("Installer is located in a folder that has existing game files. Please make sure the installer is in a fresh folder as per the guide");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_data.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")
|
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)
|
||||||
{
|
{
|
||||||
_data = data;
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<IResult> TaskOperation()
|
// extract release files
|
||||||
|
SetStatus("Extracting Release", "", 0);
|
||||||
|
|
||||||
|
var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, progress);
|
||||||
|
|
||||||
|
if (!extractReleaseResult.Succeeded)
|
||||||
{
|
{
|
||||||
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
return extractReleaseResult;
|
||||||
|
|
||||||
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!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
{
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async override Task<IResult> TaskOperation()
|
return Result.FromSuccess();
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 () =>
|
||||||
{
|
{
|
||||||
get => _currentTask;
|
var result = await installer.RunTasks();
|
||||||
set => this.RaiseAndSetIfChanged(ref _currentTask, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableCollection<InstallerTaskBase> MyTasks { get; set; }
|
NavigateTo(new MessageViewModel(HostScreen, result));
|
||||||
= 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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
get => _HasErrors;
|
desktopApp.MainWindow.Close();
|
||||||
set => this.RaiseAndSetIfChanged(ref _HasErrors, value);
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public MessageViewModel(IScreen Host, IResult result) : base(Host)
|
||||||
|
{
|
||||||
|
Message = result.Message;
|
||||||
|
|
||||||
|
if(result.Succeeded)
|
||||||
|
{
|
||||||
|
Log.Information(Message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _Message;
|
HasErrors = true;
|
||||||
public string Message
|
Log.Error(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using SPTInstaller.Aki.Helper;
|
|
||||||
using SPTInstaller.Controllers;
|
using SPTInstaller.Controllers;
|
||||||
using SPTInstaller.Helpers;
|
using SPTInstaller.Helpers;
|
||||||
using SPTInstaller.Models;
|
using SPTInstaller.Models;
|
||||||
|
@ -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