Merge pull request 'master' (#3) from CWX/SPT-AKI-Installer:master into master
Reviewed-on: waffle.lord/SPT-AKI-Installer#3
This commit is contained in:
commit
4f0eba1ac8
@ -1,4 +1,4 @@
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:SPTInstaller"
|
||||
x:Class="SPTInstaller.App">
|
||||
|
@ -5,7 +5,7 @@
|
||||
>
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Spacing="5" Background="{StaticResource AKI_Background_Dark}">
|
||||
<Button Content="Blah"/>
|
||||
<Button Content="Blah" Classes="outlinedTLCorner"/>
|
||||
<TextBox Text="Some cool text here" Margin="5"/>
|
||||
<TextBox Watermark="This is a watermark" Margin="5"/>
|
||||
</StackPanel>
|
||||
@ -64,6 +64,11 @@
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<!-- TextBlock Styles -->
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Label Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Label.xaml -->
|
||||
<Style Selector="Label">
|
||||
@ -163,25 +168,41 @@
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button Link Style -->
|
||||
<Style Selector="Button.link">
|
||||
<!-- Button outlined Style -->
|
||||
<Style Selector="Button.outlinedTLCorner">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="2 2 0 0"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pointerover /template/ ContentPresenter">
|
||||
<Style Selector="Button.outlinedTLCorner:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="BorderThickness" Value="1 1 0 0"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pressed /template/ ContentPresenter">
|
||||
<Style Selector="Button.outlinedTLCorner:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<!-- PreCheck Path Styles -->
|
||||
<Style Selector="Path.passed">
|
||||
<Setter Property="Data" Value="{StaticResource CircledCheck}"/>
|
||||
<Setter Property="Fill" Value="Green"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.failed">
|
||||
<Setter Property="Data" Value="{StaticResource CircledX}"/>
|
||||
<Setter Property="Fill" Value="Red"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.warning">
|
||||
<Setter Property="Data" Value="{StaticResource CircledWarn}"/>
|
||||
<Setter Property="Fill" Value="Goldenrod"/>
|
||||
</Style>
|
||||
</Styles>
|
@ -32,7 +32,7 @@ public class InstallController
|
||||
{
|
||||
var result = await check.RunCheck();
|
||||
|
||||
Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}");
|
||||
Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}\nDetail: {check.PreCheckDetails.ReplaceLineEndings(" ")}");
|
||||
|
||||
if (check.IsRequired)
|
||||
{
|
||||
|
83
SPTInstaller/CustomControls/DetailedPreCheckItem.axaml
Normal file
83
SPTInstaller/CustomControls/DetailedPreCheckItem.axaml
Normal file
@ -0,0 +1,83 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.DetailedPreCheckItem"
|
||||
Background="Transparent" MinHeight="100">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Arc.running">
|
||||
<Setter Property="Stroke" Value="DodgerBlue"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Arc">
|
||||
<Setter Property="Stroke" Value="Gray"/>
|
||||
<Setter Property="IsVisible" Value="{Binding IsPending, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:1" RepeatCount="infinite">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="RotateTransform.Angle" Value="0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="RotateTransform.Angle" Value="360"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid RowDefinitions="3,AUTO,3,*,30,3" ColumnDefinitions="3,*,AUTO,3" Margin="10">
|
||||
<Border Grid.RowSpan="5" Grid.ColumnSpan="3"
|
||||
Background="{StaticResource AKI_Background_Light}" CornerRadius="8"
|
||||
BoxShadow="3 3 10 .1 black"
|
||||
/>
|
||||
<Border Grid.RowSpan="3" Grid.ColumnSpan="3"
|
||||
Background="{StaticResource AKI_Brush_DarkGrayBlue}" CornerRadius="8 8 0 0"
|
||||
/>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="1" ColumnDefinitions="22, AUTO" Margin="3">
|
||||
<Canvas Margin="0 3 0 0"
|
||||
IsVisible="{Binding !IsPending, RelativeSource={RelativeSource AncestorType=UserControl}}">
|
||||
<Ellipse Fill="White" Height="15" Width="15" Canvas.Top="3" Canvas.Left="3"
|
||||
/>
|
||||
<Path Name="iconPath" StrokeThickness="2"
|
||||
Classes.passed="{Binding Passed, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
>
|
||||
<Classes.failed>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsRequired"/>
|
||||
<Binding Path="!Passed"/>
|
||||
</MultiBinding>
|
||||
</Classes.failed>
|
||||
<Classes.warning>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="!IsRequired"/>
|
||||
<Binding Path="!Passed"/>
|
||||
</MultiBinding>
|
||||
</Classes.warning>
|
||||
</Path>
|
||||
</Canvas>
|
||||
|
||||
<Arc StartAngle="280" SweepAngle="80" Margin="0 3 0 0" StrokeThickness="3"
|
||||
Width="20" Height="20" VerticalAlignment="Top"
|
||||
Classes.running="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
<Label Grid.Column="1"
|
||||
Content="{Binding PreCheckName, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.bold="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" TextWrapping="Wrap"
|
||||
Margin="10"
|
||||
Text="{Binding PreCheckDetails, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
<Button Grid.Row="4" Grid.Column="2"
|
||||
CornerRadius="8 0 8 0" Classes="outlinedTLCorner"
|
||||
Content="{Binding ActionButtonText, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding ActionButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
IsVisible="{Binding ActionButtonIsVisible, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
48
SPTInstaller/CustomControls/DetailedPreCheckItem.axaml.cs
Normal file
48
SPTInstaller/CustomControls/DetailedPreCheckItem.axaml.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using Avalonia;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace SPTInstaller.CustomControls;
|
||||
public partial class DetailedPreCheckItem : PreCheckItem
|
||||
{
|
||||
public DetailedPreCheckItem()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string PreCheckDetails
|
||||
{
|
||||
get => GetValue(PreCheckDetailsProperty);
|
||||
set => SetValue(PreCheckDetailsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string> PreCheckDetailsProperty =
|
||||
AvaloniaProperty.Register<DetailedPreCheckItem, string>(nameof(PreCheckDetails));
|
||||
|
||||
public bool ActionButtonIsVisible
|
||||
{
|
||||
get => GetValue(ActionButtonIsVisibleProperty);
|
||||
set => SetValue(ActionButtonIsVisibleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> ActionButtonIsVisibleProperty =
|
||||
AvaloniaProperty.Register<DetailedPreCheckItem, bool>(nameof(ActionButtonIsVisible));
|
||||
|
||||
public string ActionButtonText
|
||||
{
|
||||
get => GetValue(ActionButtonTextProperty);
|
||||
set => SetValue(ActionButtonTextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string> ActionButtonTextProperty =
|
||||
AvaloniaProperty.Register<DetailedPreCheckItem, string>(nameof(ActionButtonText));
|
||||
|
||||
public ICommand ActionButtonCommand
|
||||
{
|
||||
get => GetValue(ActionButtonCommandProperty);
|
||||
set => SetValue(ActionButtonCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ICommand> ActionButtonCommandProperty =
|
||||
AvaloniaProperty.Register<DetailedPreCheckItem, ICommand>(nameof(ActionButtonCommand));
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@ -6,7 +6,6 @@
|
||||
x:Class="SPTInstaller.CustomControls.PreCheckItem">
|
||||
|
||||
<UserControl.Styles>
|
||||
|
||||
<Style Selector="Arc.running">
|
||||
<Setter Property="Stroke" Value="DodgerBlue"/>
|
||||
</Style>
|
||||
@ -29,23 +28,6 @@
|
||||
<Style Selector="Label.bold">
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.passed">
|
||||
<Setter Property="Data" Value="{StaticResource CircledCheck}"/>
|
||||
<Setter Property="Fill" Value="Green"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.failed">
|
||||
<Setter Property="Data" Value="{StaticResource CircledX}"/>
|
||||
<Setter Property="Fill" Value="Red"/>
|
||||
<Setter Property="ToolTip.Tip" Value="A required dependency could not be found"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.warning">
|
||||
<Setter Property="Data" Value="{StaticResource CircledWarn}"/>
|
||||
<Setter Property="Fill" Value="Goldenrod"/>
|
||||
<Setter Property="ToolTip.Tip" Value="This dependency could not be found"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
|
||||
|
@ -1,29 +1,38 @@
|
||||
using System.Linq;
|
||||
using Serilog;
|
||||
|
||||
namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class DirectorySizeHelper
|
||||
{
|
||||
public static bool CheckAvailableSize(string eftSourceDirPath, string installTargetDirPath)
|
||||
// SizeSuffix implementation found here:
|
||||
// https://stackoverflow.com/a/14488941
|
||||
static readonly string[] SizeSuffixes =
|
||||
{ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
|
||||
public static string SizeSuffix(Int64 value, int decimalPlaces = 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var eftSourceDirectoryInfo = new DirectoryInfo(eftSourceDirPath);
|
||||
var installTargetDirectoryInfo = new DirectoryInfo(installTargetDirPath);
|
||||
|
||||
var eftSourceDirSize = GetSizeOfDirectory(eftSourceDirectoryInfo);
|
||||
var availableSize = DriveInfo.GetDrives().FirstOrDefault(d => d.Name == installTargetDirectoryInfo.Root.Name)?.AvailableFreeSpace ?? 0;
|
||||
if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
|
||||
if (value < 0) { return "-" + SizeSuffix(-value, decimalPlaces); }
|
||||
if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }
|
||||
|
||||
return eftSourceDirSize < availableSize;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while checking available size");
|
||||
// mag is 0 for bytes, 1 for KB, 2, for MB, etc.
|
||||
int mag = (int)Math.Log(value, 1024);
|
||||
|
||||
return false;
|
||||
// 1L << (mag * 10) == 2 ^ (10 * mag)
|
||||
// [i.e. the number of bytes in the unit corresponding to mag]
|
||||
decimal adjustedSize = (decimal)value / (1L << (mag * 10));
|
||||
|
||||
// make adjustment when the value is large enough that
|
||||
// it would round up to 1000 or more
|
||||
if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
|
||||
{
|
||||
mag += 1;
|
||||
adjustedSize /= 1024;
|
||||
}
|
||||
|
||||
return string.Format("{0:n" + decimalPlaces + "} {1}",
|
||||
adjustedSize,
|
||||
SizeSuffixes[mag]);
|
||||
}
|
||||
|
||||
private static long GetSizeOfDirectory(DirectoryInfo sourceDir) => sourceDir.EnumerateFiles("*", SearchOption.AllDirectories).Sum(fi => fi.Length);
|
||||
|
||||
public static long GetSizeOfDirectory(DirectoryInfo sourceDir) => sourceDir.EnumerateFiles("*", SearchOption.AllDirectories).Sum(fi => fi.Length);
|
||||
}
|
@ -16,16 +16,7 @@ public class InitializationTask : InstallerTaskBase
|
||||
|
||||
public override async Task<IResult> TaskOperation()
|
||||
{
|
||||
SetStatus("Initializing", $"Target Install Path: {FileHelper.GetRedactedPath(_data.TargetInstallPath)}");
|
||||
|
||||
_data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
||||
|
||||
if (_data.OriginalGamePath == null)
|
||||
{
|
||||
return Result.FromError("EFT IS NOT INSTALLED!");
|
||||
}
|
||||
|
||||
SetStatus(null, $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}");
|
||||
SetStatus("Initializing", $"Installed EFT Game Path: {FileHelper.GetRedactedPath(_data.OriginalGamePath)}");
|
||||
|
||||
var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
@ -13,13 +14,35 @@ public class FreeSpacePreCheck : PreCheckBase
|
||||
_internalData = internalData;
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckOperation()
|
||||
public override async Task<PreCheckResult> CheckOperation()
|
||||
{
|
||||
if (_internalData.OriginalGamePath is null || _internalData.TargetInstallPath is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_internalData.OriginalGamePath is null)
|
||||
return PreCheckResult.FromError("Could not find EFT game path");
|
||||
|
||||
return DirectorySizeHelper.CheckAvailableSize(_internalData.OriginalGamePath, _internalData.TargetInstallPath);
|
||||
if (_internalData.TargetInstallPath is null)
|
||||
return PreCheckResult.FromError("Could not find install target path");
|
||||
|
||||
try
|
||||
{
|
||||
var eftSourceDirectoryInfo = new DirectoryInfo(_internalData.OriginalGamePath);
|
||||
var installTargetDirectoryInfo = new DirectoryInfo(_internalData.TargetInstallPath);
|
||||
|
||||
var eftSourceDirSize = DirectorySizeHelper.GetSizeOfDirectory(eftSourceDirectoryInfo);
|
||||
var availableSize = DriveInfo.GetDrives().FirstOrDefault(d => d.Name.ToLower() == installTargetDirectoryInfo.Root.Name.ToLower())?.AvailableFreeSpace ?? 0;
|
||||
|
||||
var availableSpaceMessage = $"Available Space: {DirectorySizeHelper.SizeSuffix(availableSize, 2)}";
|
||||
var requiredSpaceMessage = $"Space Required for EFT Client: {DirectorySizeHelper.SizeSuffix(eftSourceDirSize, 2)}";
|
||||
|
||||
if (eftSourceDirSize > availableSize)
|
||||
{
|
||||
return PreCheckResult.FromError($"Not enough free space on {installTargetDirectoryInfo.Root.Name} to install SPT\n\n{availableSpaceMessage}\n{requiredSpaceMessage}");
|
||||
}
|
||||
|
||||
return PreCheckResult.FromSuccess($"There is enough space available on {installTargetDirectoryInfo.Root.Name} to install SPT.\n\n{availableSpaceMessage}\n{requiredSpaceMessage}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return PreCheckResult.FromException(ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,11 +10,24 @@ public class NetCore6PreCheck : PreCheckBase
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckOperation()
|
||||
public override async Task<PreCheckResult> CheckOperation()
|
||||
{
|
||||
var minRequiredVersion = new Version("6.0.0");
|
||||
string[] output;
|
||||
|
||||
var failedButtonText = "Download .Net Core 6 Desktop Runtime";
|
||||
|
||||
var failedButtonAction = () =>
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ArgumentList = { "/C", "start", "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.4-windows-x64-installer" }
|
||||
});
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var proc = Process.Start(new ProcessStartInfo()
|
||||
@ -32,9 +45,11 @@ public class NetCore6PreCheck : PreCheckBase
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: logging
|
||||
return false;
|
||||
return PreCheckResult.FromException(ex);
|
||||
}
|
||||
|
||||
var highestFoundVersion = new Version("0.0.0");
|
||||
|
||||
foreach (var lineVersion in output)
|
||||
{
|
||||
if (lineVersion.StartsWith("Microsoft.WindowsDesktop.App") && lineVersion.Split(" ").Length > 1)
|
||||
@ -43,14 +58,16 @@ public class NetCore6PreCheck : PreCheckBase
|
||||
|
||||
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
|
||||
// waffle: not fully sure if we should only check for 6.x.x versions or if higher major versions are ok
|
||||
if (foundVersion >= minRequiredVersion)
|
||||
{
|
||||
return true;
|
||||
return PreCheckResult.FromSuccess($".Net Core {minRequiredVersion} Desktop Runtime or higher is installed.\n\nInstalled Version: {foundVersion}");
|
||||
}
|
||||
|
||||
highestFoundVersion = foundVersion > highestFoundVersion ? foundVersion : highestFoundVersion;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return PreCheckResult.FromError($".Net Core Desktop Runtime version {minRequiredVersion} or higher is required.\n\nHighest Version Found: {(highestFoundVersion > new Version("0.0.0") ? highestFoundVersion : "Not Found")}\n\nThis is required to play SPT, but you can install it later if and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using Microsoft.Win32;
|
||||
using SPTInstaller.Models;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPTInstaller.Installer_Tasks.PreChecks;
|
||||
@ -10,7 +11,7 @@ public class NetFramework472PreCheck : PreCheckBase
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<bool> CheckOperation()
|
||||
public override async Task<PreCheckResult> CheckOperation()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -18,27 +19,45 @@ public class NetFramework472PreCheck : PreCheckBase
|
||||
|
||||
var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full");
|
||||
|
||||
var failedButtonText = "Download .Net Framework 4.7.2";
|
||||
|
||||
var failedButtonAction = () =>
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ArgumentList = { "/C", "start", "https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer" }
|
||||
});
|
||||
};
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
return false;
|
||||
return PreCheckResult.FromError("Could not find .Net Framework on system.\n\nThis is required to play SPT, but you can install it later and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
|
||||
}
|
||||
|
||||
var value = key.GetValue("Version");
|
||||
|
||||
if (value != null && value is string versionString)
|
||||
if (value == null || value is not string versionString)
|
||||
{
|
||||
var installedVersion = new Version(versionString);
|
||||
|
||||
return installedVersion > minRequiredVersion;
|
||||
return PreCheckResult.FromError("Something went wrong. This precheck failed for an unknown reason. :(");
|
||||
}
|
||||
|
||||
return false;
|
||||
var installedVersion = new Version(versionString);
|
||||
|
||||
if (installedVersion < minRequiredVersion)
|
||||
{
|
||||
return PreCheckResult.FromError($".Net Framework {versionString} is installed, but {minRequiredVersion} or higher is required.\n\nYou can install it later and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
|
||||
}
|
||||
|
||||
return PreCheckResult.FromSuccess($".Net Framework {minRequiredVersion} or higher is installed.\n\nInstalled Version: {installedVersion}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: log exceptions
|
||||
|
||||
return false;
|
||||
return PreCheckResult.FromException(ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -12,5 +12,7 @@ public interface IPreCheck
|
||||
|
||||
public bool Passed { get; }
|
||||
|
||||
public string PreCheckDetails { get; }
|
||||
|
||||
public Task<IResult> RunCheck();
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using ReactiveUI;
|
||||
using SPTInstaller.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
@ -48,6 +49,34 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
set => this.RaiseAndSetIfChanged(ref _isRunning, value);
|
||||
}
|
||||
|
||||
private string _preCheckDetails;
|
||||
public string PreCheckDetails
|
||||
{
|
||||
get => _preCheckDetails;
|
||||
set => this.RaiseAndSetIfChanged(ref _preCheckDetails, value);
|
||||
}
|
||||
|
||||
private bool _actionButtonIsVisible;
|
||||
public bool ActionButtonIsVisible
|
||||
{
|
||||
get => _actionButtonIsVisible;
|
||||
set => this.RaiseAndSetIfChanged(ref _actionButtonIsVisible, value);
|
||||
}
|
||||
|
||||
private string _actionButtonText;
|
||||
public string ActionButtonText
|
||||
{
|
||||
get => _actionButtonText;
|
||||
set => this.RaiseAndSetIfChanged(ref _actionButtonText, value);
|
||||
}
|
||||
|
||||
private ICommand _actionButtonCommand;
|
||||
public ICommand ActionButtonCommand
|
||||
{
|
||||
get => _actionButtonCommand;
|
||||
set => this.RaiseAndSetIfChanged(ref _actionButtonCommand, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for pre-checks to run before installation
|
||||
/// </summary>
|
||||
@ -63,12 +92,23 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
public async Task<IResult> RunCheck()
|
||||
{
|
||||
IsRunning = true;
|
||||
Passed = await CheckOperation();
|
||||
|
||||
var result = await CheckOperation();
|
||||
Passed = result.Succeeded;
|
||||
|
||||
PreCheckDetails = !string.IsNullOrWhiteSpace(result.Message)
|
||||
? result.Message
|
||||
: (result.Succeeded ? "Pre-Check succeeded, but no details were provided" : "Pre-Check failed, but no details were provided");
|
||||
|
||||
ActionButtonText = result.ActionButtonText;
|
||||
ActionButtonCommand = result.ButtonPressedCommand;
|
||||
ActionButtonIsVisible = result.ActionButtonIsVisible;
|
||||
|
||||
IsRunning = false;
|
||||
IsPending = false;
|
||||
|
||||
return Passed ? Result.FromSuccess() : Result.FromError($"PreCheck Failed: {Name}");
|
||||
}
|
||||
|
||||
public abstract Task<bool> CheckOperation();
|
||||
public abstract Task<PreCheckResult> CheckOperation();
|
||||
}
|
37
SPTInstaller/Models/PreCheckResult.cs
Normal file
37
SPTInstaller/Models/PreCheckResult.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using ReactiveUI;
|
||||
using SPTInstaller.Interfaces;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
public class PreCheckResult : IResult
|
||||
{
|
||||
public bool Succeeded { get; private set; }
|
||||
|
||||
public string Message { get; private set; }
|
||||
|
||||
public bool ActionButtonIsVisible { get; private set; }
|
||||
|
||||
public string ActionButtonText { get; private set; }
|
||||
|
||||
public ICommand ButtonPressedCommand { get; private set; }
|
||||
|
||||
protected PreCheckResult(string message, bool succeeded, string actionButtonText, Action? buttonPressedAction)
|
||||
{
|
||||
Message = message;
|
||||
Succeeded = succeeded;
|
||||
|
||||
ActionButtonText = actionButtonText;
|
||||
|
||||
ActionButtonIsVisible = buttonPressedAction != null && !string.IsNullOrWhiteSpace(actionButtonText);
|
||||
|
||||
buttonPressedAction ??= () => { };
|
||||
|
||||
ButtonPressedCommand = ReactiveCommand.Create(buttonPressedAction);
|
||||
}
|
||||
|
||||
public static PreCheckResult FromSuccess(string message = "") => new PreCheckResult(message, true, "", null);
|
||||
|
||||
public static PreCheckResult FromError(string message, string actionButtonText = "", Action? actionButtonPressedAction = null) => new PreCheckResult(message, false, actionButtonText, actionButtonPressedAction);
|
||||
|
||||
public static PreCheckResult FromException(Exception ex, string actionButtonText = "", Action? actionButtonPressedAction = null) => new PreCheckResult($"An exception was thrown during this precheck\n\nException:\n{ex.Message}\n\nStacktrace:\n{ex.StackTrace}", false, actionButtonText, actionButtonPressedAction);
|
||||
}
|
@ -9,8 +9,8 @@
|
||||
<PackageIcon>icon.ico</PackageIcon>
|
||||
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;TEST</Configurations>
|
||||
<AssemblyVersion>2.3</AssemblyVersion>
|
||||
<FileVersion>2.3</FileVersion>
|
||||
<AssemblyVersion>2.5</AssemblyVersion>
|
||||
<FileVersion>2.5</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
9
SPTInstaller/ViewModels/DetailedPreChecksViewModel.cs
Normal file
9
SPTInstaller/ViewModels/DetailedPreChecksViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using ReactiveUI;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
public class DetailedPreChecksViewModel : PreChecksViewModel
|
||||
{
|
||||
public DetailedPreChecksViewModel(IScreen host) : base(host)
|
||||
{
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using SPTInstaller.Controllers;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
@ -15,6 +16,8 @@ public class PreChecksViewModel : ViewModelBase
|
||||
|
||||
public ObservableCollection<PreCheckBase> PreChecks { get; set; } = new(ServiceHelper.GetAll<PreCheckBase>());
|
||||
public ICommand StartInstallCommand { get; set; }
|
||||
public ICommand ShowDetailedViewCommand { get; set; }
|
||||
|
||||
public string InstallPath
|
||||
{
|
||||
get => _installPath;
|
||||
@ -39,10 +42,21 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
||||
|
||||
if (data.OriginalGamePath == null)
|
||||
{
|
||||
NavigateTo(new MessageViewModel(HostScreen, Result.FromError("Could not find EFT install.\n\nDo you own and have the game installed?")));
|
||||
}
|
||||
|
||||
data.TargetInstallPath = Environment.CurrentDirectory;
|
||||
InstallPath = data.TargetInstallPath;
|
||||
|
||||
StartInstallCommand = ReactiveCommand.Create(() => NavigateTo(new InstallViewModel(HostScreen)));
|
||||
ShowDetailedViewCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
Log.Logger.Information("Opening Detailed PreCheck View");
|
||||
NavigateTo(new DetailedPreChecksViewModel(HostScreen));
|
||||
});
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
|
61
SPTInstaller/Views/DetailedPreChecksView.axaml
Normal file
61
SPTInstaller/Views/DetailedPreChecksView.axaml
Normal file
@ -0,0 +1,61 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:cc="using:SPTInstaller.CustomControls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.DetailedPreChecksView">
|
||||
<Grid RowDefinitions="10,AUTO,AUTO,AUTO,*,10" ColumnDefinitions="10,AUTO,*,AUTO,10">
|
||||
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center"
|
||||
Content="SPT will be installed into:"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3"
|
||||
Foreground="DodgerBlue" FontWeight="SemiBold" FontSize="16"
|
||||
Text="{Binding InstallPath}" TextWrapping="Wrap"
|
||||
Margin="5"
|
||||
/>
|
||||
|
||||
<Button Grid.Row="1" Grid.RowSpan="3" Grid.Column="3"
|
||||
Content="Start Install" Padding="20 10"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="15" FontWeight="SemiBold"
|
||||
Classes="yellow"
|
||||
IsEnabled="{Binding AllowInstall}"
|
||||
Command="{Binding StartInstallCommand}"
|
||||
/>
|
||||
|
||||
<ScrollViewer Grid.Row="4" Grid.ColumnSpan="5">
|
||||
<ItemsControl Items="{Binding PreChecks}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel HorizontalAlignment="Stretch" Margin="10 0"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<cc:DetailedPreCheckItem PreCheckName="{Binding Name}"
|
||||
IsRunning="{Binding IsRunning}"
|
||||
IsPending="{Binding IsPending}"
|
||||
IsRequired="{Binding IsRequired}"
|
||||
Passed="{Binding Passed}"
|
||||
PreCheckDetails="{Binding PreCheckDetails}"
|
||||
ActionButtonCommand="{Binding ActionButtonCommand}"
|
||||
ActionButtonText="{Binding ActionButtonText}"
|
||||
ActionButtonIsVisible="{Binding ActionButtonIsVisible}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<Border Grid.Row="3" Grid.ColumnSpan="5"
|
||||
BorderThickness="1" BorderBrush="Black" Height="2" Background="Black"
|
||||
BoxShadow="0 3 10 2 black"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
11
SPTInstaller/Views/DetailedPreChecksView.axaml.cs
Normal file
11
SPTInstaller/Views/DetailedPreChecksView.axaml.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using SPTInstaller.ViewModels;
|
||||
|
||||
namespace SPTInstaller.Views;
|
||||
public partial class DetailedPreChecksView : ReactiveUserControl<DetailedPreChecksViewModel>
|
||||
{
|
||||
public DetailedPreChecksView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@ -6,7 +6,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.PreChecksView">
|
||||
<Grid ColumnDefinitions="10,*,AUTO,*,10"
|
||||
RowDefinitions="10,*,AUTO,AUTO,AUTO,*,10">
|
||||
RowDefinitions="10,*,AUTO,AUTO,AUTO,AUTO,*,10">
|
||||
<StackPanel Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="2" HorizontalAlignment="Center">
|
||||
<Label Content="SPT will be installed into this folder:"
|
||||
HorizontalAlignment="Center"
|
||||
@ -44,5 +44,9 @@
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Button Grid.Column="1" Grid.Row="5" Grid.ColumnSpan="3" HorizontalAlignment="Center"
|
||||
Content="Detailed View"
|
||||
Command="{Binding ShowDetailedViewCommand}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
Loading…
x
Reference in New Issue
Block a user