major rework to move to avalonia
This commit is contained in:
parent
26db4716bc
commit
8331080d85
468
.gitignore
vendored
468
.gitignore
vendored
@ -1,20 +1,454 @@
|
|||||||
## SPTinstaller
|
|
||||||
*.exe
|
|
||||||
*.zip
|
|
||||||
bin/
|
|
||||||
obj/
|
|
||||||
*.editorconfig
|
|
||||||
|
|
||||||
## visual studio
|
Skip to content
|
||||||
.vs
|
Pull requests
|
||||||
.idea
|
Issues
|
||||||
slnx.sqlite
|
Codespaces
|
||||||
slnx-journal.sqlite
|
Marketplace
|
||||||
|
Explore
|
||||||
|
@waffle-lord
|
||||||
|
github /
|
||||||
|
gitignore
|
||||||
|
Public
|
||||||
|
|
||||||
## nodejs
|
Fork your own copy of github/gitignore
|
||||||
node_modules
|
|
||||||
node.exe
|
Code
|
||||||
package-lock.json
|
Pull requests 390
|
||||||
|
Actions
|
||||||
|
Security
|
||||||
|
|
||||||
|
Insights
|
||||||
|
|
||||||
|
gitignore/VisualStudio.gitignore
|
||||||
|
@n0099
|
||||||
|
n0099 [VisualStudio.gitignore] remove a trailing space
|
||||||
|
Latest commit 491040e Jan 26, 2022
|
||||||
|
History
|
||||||
|
165 contributors
|
||||||
|
@shiftkey
|
||||||
|
@arcresu
|
||||||
|
@aroben
|
||||||
|
@bbodenmiller
|
||||||
|
@HassanHashemi
|
||||||
|
@haacked
|
||||||
|
@niik
|
||||||
|
@AArnott
|
||||||
|
@sayedihashimi
|
||||||
|
@saschanaz
|
||||||
|
@bdougie
|
||||||
|
@OsirisTerje
|
||||||
|
398 lines (319 sloc) 6.7 KB
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
Footer
|
||||||
|
© 2023 GitHub, Inc.
|
||||||
|
Footer navigation
|
||||||
|
|
||||||
|
Terms
|
||||||
|
Privacy
|
||||||
|
Security
|
||||||
|
Status
|
||||||
|
Docs
|
||||||
|
Contact GitHub
|
||||||
|
Pricing
|
||||||
|
API
|
||||||
|
Training
|
||||||
|
Blog
|
||||||
|
About
|
||||||
|
|
||||||
## windows
|
|
||||||
desktop.ini
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 420 KiB |
@ -1,17 +0,0 @@
|
|||||||
using Spectre.Console;
|
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Interfaces
|
|
||||||
{
|
|
||||||
public interface ILiveTaskTableEntry
|
|
||||||
{
|
|
||||||
public string TaskName { get; set; }
|
|
||||||
|
|
||||||
public int RowIndex { get; set; }
|
|
||||||
|
|
||||||
public void SetContext(LiveDisplayContext context, Table table);
|
|
||||||
|
|
||||||
public Task<GenericResult> RunAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
namespace SPT_AKI_Installer.Aki.Core.Interfaces
|
|
||||||
{
|
|
||||||
internal interface IProgressableTask
|
|
||||||
{
|
|
||||||
public int Progress { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
namespace SPT_AKI_Installer.Aki.Core.Model
|
|
||||||
{
|
|
||||||
public class GenericResult
|
|
||||||
{
|
|
||||||
public string Message { get; private set; }
|
|
||||||
|
|
||||||
public bool Succeeded { get; private set; }
|
|
||||||
public bool NonCritical { get; private set; }
|
|
||||||
|
|
||||||
protected GenericResult(string message, bool succeeded, bool nonCritical = false)
|
|
||||||
{
|
|
||||||
Message = message;
|
|
||||||
Succeeded = succeeded;
|
|
||||||
NonCritical = nonCritical;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GenericResult FromSuccess(string message = "") => new GenericResult(message, true);
|
|
||||||
public static GenericResult FromError(string errorMessage) => new GenericResult(errorMessage, false);
|
|
||||||
public static GenericResult FromWarning(string warningMessage) => new GenericResult(warningMessage, false, true);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,159 +0,0 @@
|
|||||||
using Spectre.Console;
|
|
||||||
using SPT_AKI_Installer.Aki.Core.Interfaces;
|
|
||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Model
|
|
||||||
{
|
|
||||||
public abstract class LiveTableTask : ILiveTaskTableEntry, IProgressableTask, IDisposable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The name that will be displayed in th first column of the table
|
|
||||||
/// </summary>
|
|
||||||
public string TaskName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wheather the task reports progress or not
|
|
||||||
/// </summary>
|
|
||||||
public bool IsIndeterminate;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The progress (percent completed) of the task
|
|
||||||
/// </summary>
|
|
||||||
public int Progress { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The row index in the table of the task
|
|
||||||
/// </summary>
|
|
||||||
public int RowIndex { get; set; }
|
|
||||||
|
|
||||||
private bool _continueRenderingProgress = false;
|
|
||||||
private bool _continueRenderingIndeterminateProgress = false;
|
|
||||||
|
|
||||||
private int _indeterminateState = 0;
|
|
||||||
|
|
||||||
private string _currentStatus = "running";
|
|
||||||
|
|
||||||
private Table _table { get; set; }
|
|
||||||
|
|
||||||
private LiveDisplayContext _context { get; set; }
|
|
||||||
|
|
||||||
public LiveTableTask(string name, bool isIndeterminate = true)
|
|
||||||
{
|
|
||||||
TaskName = name;
|
|
||||||
IsIndeterminate = isIndeterminate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetIndetermminateStatus()
|
|
||||||
{
|
|
||||||
string status = $"[blue]{_currentStatus.EscapeMarkup()} ";
|
|
||||||
|
|
||||||
if (_indeterminateState > 3) _indeterminateState = 0;
|
|
||||||
|
|
||||||
status += new string('.', _indeterminateState);
|
|
||||||
|
|
||||||
status += "[/]";
|
|
||||||
|
|
||||||
_indeterminateState++;
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Start indeterminate progress spinner
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>this doesn't need to be called if you set isIndeterminate in the constructor. You need to set IsIndeterminate to false to stop this background task</remarks>
|
|
||||||
public void StartDrawingIndeterminateProgress()
|
|
||||||
{
|
|
||||||
_continueRenderingProgress = false;
|
|
||||||
_continueRenderingIndeterminateProgress = true;
|
|
||||||
|
|
||||||
new Task(new Action(() => { RenderIndeterminateProgress(ref _continueRenderingIndeterminateProgress); })).Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartDrawingProgress()
|
|
||||||
{
|
|
||||||
Progress = 0;
|
|
||||||
_continueRenderingIndeterminateProgress = false;
|
|
||||||
_continueRenderingProgress = true;
|
|
||||||
|
|
||||||
new Task(new Action(() => { RenderProgress(ref _continueRenderingProgress); })).Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReRenderEntry(string message)
|
|
||||||
{
|
|
||||||
_table.RemoveRow(RowIndex);
|
|
||||||
_table.InsertRow(RowIndex, TaskName, message);
|
|
||||||
_context.Refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderIndeterminateProgress(ref bool continueRendering)
|
|
||||||
{
|
|
||||||
while (continueRendering)
|
|
||||||
{
|
|
||||||
ReRenderEntry(GetIndetermminateStatus());
|
|
||||||
Thread.Sleep(300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderProgress(ref bool continueRendering)
|
|
||||||
{
|
|
||||||
while (continueRendering)
|
|
||||||
{
|
|
||||||
string progressBar = new string(' ', 10);
|
|
||||||
|
|
||||||
int progressFill = (int)Math.Floor((double)Progress / 10);
|
|
||||||
|
|
||||||
progressBar = progressBar.Remove(0, progressFill).Insert(0, new string('=', progressFill));
|
|
||||||
|
|
||||||
progressBar = $"[blue][[{progressBar}]][/] {Progress}% {_currentStatus}";
|
|
||||||
|
|
||||||
ReRenderEntry(progressBar);
|
|
||||||
|
|
||||||
Thread.Sleep(300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the context and table for this task
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context"></param>
|
|
||||||
/// <param name="table"></param>
|
|
||||||
/// <remarks>This is called by <see cref="LiveTableTaskRunner"/> when it is ran. No need to call it yourself</remarks>
|
|
||||||
public void SetContext(LiveDisplayContext context, Table table)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
_table = table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the status text for the task
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">The message to show</param>
|
|
||||||
/// <param name="stopRendering">Stop rendering progress updates (progress and indeterminate progress tasks)</param>
|
|
||||||
/// <remarks>If you are running an indeterminate task, set render to false. It will render at the next indeterminate update interval</remarks>
|
|
||||||
public void SetStatus(string message, bool stopRendering = true)
|
|
||||||
{
|
|
||||||
_currentStatus = message;
|
|
||||||
|
|
||||||
if (stopRendering)
|
|
||||||
{
|
|
||||||
_continueRenderingProgress = false;
|
|
||||||
_continueRenderingIndeterminateProgress = false;
|
|
||||||
ReRenderEntry(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Run the task async
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Returns the result of the task</returns>
|
|
||||||
public abstract Task<GenericResult> RunAsync();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
IsIndeterminate = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
using Spectre.Console;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Model
|
|
||||||
{
|
|
||||||
public class LiveTableTaskRunner
|
|
||||||
{
|
|
||||||
private static async Task<(bool, LiveTableTask task)> RunAllTasksAsync(List<LiveTableTask> tasks, LiveDisplayContext context, Table table)
|
|
||||||
{
|
|
||||||
foreach (var task in tasks)
|
|
||||||
{
|
|
||||||
if (task.IsIndeterminate)
|
|
||||||
{
|
|
||||||
task.StartDrawingIndeterminateProgress();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
task.StartDrawingProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await task.RunAsync();
|
|
||||||
|
|
||||||
// critical: error - stop installer
|
|
||||||
if (!result.Succeeded && !result.NonCritical)
|
|
||||||
{
|
|
||||||
task.SetStatus($"[red]{result.Message.EscapeMarkup()}[/]");
|
|
||||||
return (false, task);
|
|
||||||
}
|
|
||||||
|
|
||||||
// non-critical: warning - continue
|
|
||||||
if (!result.Succeeded && result.NonCritical)
|
|
||||||
{
|
|
||||||
task.SetStatus($"[yellow]{result.Message.EscapeMarkup()}[/]");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//suceeded: continue
|
|
||||||
task.SetStatus($"[green]{(result.Message == "" ? "Complete" : $"{result.Message.EscapeMarkup()}")}[/]");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true, null);
|
|
||||||
}
|
|
||||||
public static async Task RunAsync(List<LiveTableTask> tasks)
|
|
||||||
{
|
|
||||||
int halfBufferWidth = Console.BufferWidth / 2;
|
|
||||||
|
|
||||||
Table table = new Table().Alignment(Justify.Center).Border(TableBorder.Rounded).BorderColor(Color.Grey).AddColumn("Task", (column) =>
|
|
||||||
{
|
|
||||||
column.Width(halfBufferWidth);
|
|
||||||
})
|
|
||||||
.AddColumn("Status", (column) =>
|
|
||||||
{
|
|
||||||
column.Width(halfBufferWidth);
|
|
||||||
});
|
|
||||||
|
|
||||||
await AnsiConsole.Live(table).StartAsync(async context =>
|
|
||||||
{
|
|
||||||
foreach (var task in tasks)
|
|
||||||
{
|
|
||||||
table.AddRow(task.TaskName, "[purple]Pending[/]");
|
|
||||||
task.RowIndex = table.Rows.Count() - 1;
|
|
||||||
task.SetContext(context, table);
|
|
||||||
|
|
||||||
await Task.Delay(50);
|
|
||||||
context.Refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await RunAllTasksAsync(tasks, context, table);
|
|
||||||
|
|
||||||
// if a task failed and returned early, set any remaining task status to cancelled
|
|
||||||
if (!result.Item1)
|
|
||||||
{
|
|
||||||
var cancelledTasks = tasks.Take(new Range(tasks.IndexOf(result.Item2) + 1, tasks.Count));
|
|
||||||
|
|
||||||
foreach (var task in cancelledTasks)
|
|
||||||
{
|
|
||||||
task.SetStatus("[grey]Cancelled[/]");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Spectre.Console;
|
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
|
||||||
using SPT_AKI_Installer.Aki.Core.Tasks;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core
|
|
||||||
{
|
|
||||||
//TODO:
|
|
||||||
// locales, language selection
|
|
||||||
|
|
||||||
public class SPTinstaller
|
|
||||||
{
|
|
||||||
InternalData _data;
|
|
||||||
static void Main()
|
|
||||||
{
|
|
||||||
var host = ConfigureHost();
|
|
||||||
|
|
||||||
var tasks = host.Services.GetServices<LiveTableTask>();
|
|
||||||
|
|
||||||
var taskList = new List<LiveTableTask>(tasks);
|
|
||||||
|
|
||||||
AnsiConsole.Write(new FigletText("SPT-AKI INSTALLER").Centered().Color(Color.Yellow));
|
|
||||||
|
|
||||||
host.Services.GetRequiredService<SPTinstaller>()
|
|
||||||
.RunTasksAsync(taskList)
|
|
||||||
.GetAwaiter()
|
|
||||||
.GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SPTinstaller(
|
|
||||||
InternalData data
|
|
||||||
)
|
|
||||||
{
|
|
||||||
_data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RunTasksAsync(List<LiveTableTask> tasks)
|
|
||||||
{
|
|
||||||
_data.TargetInstallPath = Environment.CurrentDirectory;
|
|
||||||
|
|
||||||
var cursorPos = Console.GetCursorPosition();
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
var path = AnsiConsole.Ask<string>("[purple]DEBUG[/] [blue]::[/] Enter path to install folder: ").Replace("\"", "");
|
|
||||||
|
|
||||||
if (!Directory.Exists(path))
|
|
||||||
{
|
|
||||||
CloseApp($"Path invalid: {path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
_data.TargetInstallPath = path;
|
|
||||||
#endif
|
|
||||||
var continueInstall = AnsiConsole.Confirm($"SPT will install into:\n[blue]{_data.TargetInstallPath}[/]\n\nContinue?", false);
|
|
||||||
|
|
||||||
if (!continueInstall) CloseApp("Please move the installer to the folder you want to install into");
|
|
||||||
|
|
||||||
|
|
||||||
Console.SetCursorPosition(cursorPos.Left, cursorPos.Top);
|
|
||||||
await LiveTableTaskRunner.RunAsync(tasks);
|
|
||||||
CloseApp("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IHost ConfigureHost()
|
|
||||||
{
|
|
||||||
return Host.CreateDefaultBuilder().ConfigureServices((_, services) =>
|
|
||||||
{
|
|
||||||
services.AddSingleton<InternalData>();
|
|
||||||
services.AddTransient<LiveTableTask, DependencyCheckTask>();
|
|
||||||
services.AddTransient<LiveTableTask, InitializationTask>();
|
|
||||||
services.AddTransient<LiveTableTask, ReleaseCheckTask>();
|
|
||||||
services.AddTransient<LiveTableTask, DownloadTask>();
|
|
||||||
services.AddTransient<LiveTableTask, CopyClientTask>();
|
|
||||||
services.AddTransient<LiveTableTask, SetupClientTask>();
|
|
||||||
services.AddTransient<SPTinstaller>();
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void CloseApp(string text)
|
|
||||||
{
|
|
||||||
AnsiConsole.MarkupLine($"[yellow]{text.EscapeMarkup()}[/]");
|
|
||||||
AnsiConsole.WriteLine();
|
|
||||||
AnsiConsole.MarkupLine("Press [blue]Enter[/] to close ...");
|
|
||||||
Console.ReadKey();
|
|
||||||
Environment.Exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
using Microsoft.Win32;
|
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data.SqlTypes;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|
||||||
{
|
|
||||||
public class DependencyCheckTask : LiveTableTask
|
|
||||||
{
|
|
||||||
private bool CheckNetCore6Installed()
|
|
||||||
{
|
|
||||||
var minRequiredVersion = new Version("6.0.0");
|
|
||||||
string[] output;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var proc = Process.Start(new ProcessStartInfo()
|
|
||||||
{
|
|
||||||
FileName = "dotnet",
|
|
||||||
Arguments = "--list-runtimes",
|
|
||||||
RedirectStandardOutput = true
|
|
||||||
});
|
|
||||||
|
|
||||||
proc.WaitForExit();
|
|
||||||
|
|
||||||
output = proc.StandardOutput.ReadToEnd().Split("\r\n");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CheckNet472Installed()
|
|
||||||
{
|
|
||||||
var minRequiredVersion = new Version("4.7.2");
|
|
||||||
|
|
||||||
var key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full");
|
|
||||||
|
|
||||||
if (key == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = key.GetValue("Version");
|
|
||||||
|
|
||||||
if (value != null && value is string versionString)
|
|
||||||
{
|
|
||||||
var installedVersion = new Version(versionString);
|
|
||||||
|
|
||||||
return installedVersion > minRequiredVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DependencyCheckTask() : base("Dependency Checks", true)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
GenericResult getResult(bool net472Check, bool netCoreCheck) =>
|
|
||||||
(net472Check, netCoreCheck) switch
|
|
||||||
{
|
|
||||||
(true, true) => GenericResult.FromSuccess("Dependencies already installed"),
|
|
||||||
(false, true) => GenericResult.FromWarning(".Net Framework 472 not found.\nCheck SPT release page for requirements\nhttps://hub.sp-tarkov.com/files/file/16-spt-aki/"),
|
|
||||||
(true, false) => GenericResult.FromWarning(".Net Runtime Desktop 6 not found.\nCheck SPT release page for requirements\nhttps://hub.sp-tarkov.com/files/file/16-spt-aki/"),
|
|
||||||
(false, false) => GenericResult.FromWarning("Required dependencies not found.\nCheck SPT release page for requirements\nhttps://hub.sp-tarkov.com/files/file/16-spt-aki/")
|
|
||||||
};
|
|
||||||
|
|
||||||
public override Task<GenericResult> RunAsync()
|
|
||||||
{
|
|
||||||
SetStatus("Checking for net framework");
|
|
||||||
|
|
||||||
var net472Check = CheckNet472Installed();
|
|
||||||
|
|
||||||
SetStatus("Checking for net core");
|
|
||||||
|
|
||||||
var netCoreCheck = CheckNetCore6Installed();
|
|
||||||
|
|
||||||
return Task.FromResult(getResult(net472Check, netCoreCheck));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Helper
|
|
||||||
{
|
|
||||||
public static class FileHelper
|
|
||||||
{
|
|
||||||
public static GenericResult CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, IProgress<double> progress)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length;
|
|
||||||
int processedFiles = 0;
|
|
||||||
|
|
||||||
foreach (var dir in sourceDir.GetDirectories("*", SearchOption.AllDirectories))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(dir.FullName.Replace(sourceDir.FullName, targetDir.FullName));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories))
|
|
||||||
{
|
|
||||||
File.Copy(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName), true);
|
|
||||||
processedFiles++;
|
|
||||||
|
|
||||||
progress.Report((int)Math.Floor(((double)processedFiles / totalFiles) * 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return GenericResult.FromError(ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
|
||||||
-->
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<Configuration>Release</Configuration>
|
|
||||||
<Platform>Any CPU</Platform>
|
|
||||||
<PublishDir>bin\Release\net6.0\publish\win-x64\</PublishDir>
|
|
||||||
<PublishProtocol>FileSystem</PublishProtocol>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
|
||||||
<SelfContained>true</SelfContained>
|
|
||||||
<PublishSingleFile>true</PublishSingleFile>
|
|
||||||
<PublishReadyToRun>false</PublishReadyToRun>
|
|
||||||
<PublishTrimmed>true</PublishTrimmed>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
|
||||||
-->
|
|
||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<History>True|2022-07-12T01:15:15.4480498Z;True|2022-07-11T21:11:55.8484217-04:00;True|2022-07-09T13:06:26.5751622-04:00;True|2022-07-09T12:56:17.1018408-04:00;True|2022-07-09T12:38:17.0878078-04:00;True|2022-07-09T12:18:23.6469737-04:00;True|2022-06-21T14:47:38.7532473-04:00;True|2022-06-08T13:26:47.7977621-04:00;True|2022-06-06T10:07:18.8067168-04:00;True|2022-06-05T17:55:20.5192697-04:00;True|2022-05-30T08:11:30.6942032-04:00;True|2022-05-30T08:08:08.4269393-04:00;True|2022-05-16T20:06:33.6758525-04:00;True|2022-05-13T20:56:09.8410037-04:00;True|2022-05-13T19:54:24.0683990-04:00;True|2022-05-13T19:53:04.7105427-04:00;True|2022-05-13T19:51:00.6280767-04:00;True|2022-05-13T19:49:19.4630888-04:00;True|2022-05-13T19:47:59.2166156-04:00;</History>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
28
SPTInstaller.sln
Normal file
28
SPTInstaller.sln
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.33516.290
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SPTInstaller", "SPTInstaller\SPTInstaller.csproj", "{8637C4B3-FC40-4A76-9ED0-707FA5D8E0CF}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
TEST|Any CPU = TEST|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{8637C4B3-FC40-4A76-9ED0-707FA5D8E0CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8637C4B3-FC40-4A76-9ED0-707FA5D8E0CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8637C4B3-FC40-4A76-9ED0-707FA5D8E0CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8637C4B3-FC40-4A76-9ED0-707FA5D8E0CF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8637C4B3-FC40-4A76-9ED0-707FA5D8E0CF}.TEST|Any CPU.ActiveCfg = TEST|Any CPU
|
||||||
|
{8637C4B3-FC40-4A76-9ED0-707FA5D8E0CF}.TEST|Any CPU.Build.0 = TEST|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {AF73BCF4-41F2-472E-9C5B-B7F3B71FA6B5}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
454
SPTInstaller/.gitignore
vendored
Normal file
454
SPTInstaller/.gitignore
vendored
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# Tye
|
||||||
|
.tye/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual studio for Mac
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual Studio Code
|
||||||
|
##
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
39
SPTInstaller/App.axaml
Normal file
39
SPTInstaller/App.axaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="using:SPTInstaller"
|
||||||
|
x:Class="SPTInstaller.App">
|
||||||
|
<Application.DataTemplates>
|
||||||
|
<local:ViewLocator/>
|
||||||
|
</Application.DataTemplates>
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme Mode="Light"/>
|
||||||
|
</Application.Styles>
|
||||||
|
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
<!-- Colors -->
|
||||||
|
<Color x:Key="AKI_DarkGray">#121212</Color>
|
||||||
|
<Color x:Key="AKI_Yellow">#FFC107</Color>
|
||||||
|
<Color x:Key="AKI_White">#FFFFFF</Color>
|
||||||
|
<Color x:Key="AKI_Gray">#282828</Color>
|
||||||
|
<Color x:Key="AKI_DarkGrayBlue">#323947</Color>
|
||||||
|
|
||||||
|
<!-- Brushes -->
|
||||||
|
<SolidColorBrush x:Key="AKI_Foreground_Light" Color="{StaticResource AKI_White}"/>
|
||||||
|
<SolidColorBrush x:Key="AKI_Background_Light" Color="{StaticResource AKI_Gray}"/>
|
||||||
|
<SolidColorBrush x:Key="AKI_Background_Dark" Color="{StaticResource AKI_DarkGray}"/>
|
||||||
|
<SolidColorBrush x:Key="AKI_Brush_Yellow" Color="{StaticResource AKI_Yellow}"/>
|
||||||
|
<SolidColorBrush x:Key="AKI_Brush_DarkGrayBlue" Color="{StaticResource AKI_DarkGrayBlue}"/>
|
||||||
|
<SolidColorBrush x:Key="AKI_Brush_Lighter" Color="Gainsboro"/>
|
||||||
|
|
||||||
|
<!-- Path Geometry -->
|
||||||
|
<PathGeometry x:Key="CircledCheck" Figures="M 9.0646825 0.06313182 C 7.3648066 0.28806336 5.7978836 0.78839047 4.3639137 1.7461752 3.2921695 2.4620115 2.3631641 3.4084722 1.6479106 4.4762536 0.98737415 5.4623497 0.47819447 6.5896932 0.22806644 7.7524208 -1.2315929 14.537597 4.5254007 20.882361 11.493416 19.89881 c 1.391191 -0.196414 2.73334 -0.717402 3.917306 -1.463979 1.003459 -0.632768 1.91619 -1.463899 2.626322 -2.413989 C 22.172937 10.487163 19.448371 2.526326 12.903647 0.44688781 11.70918 0.06738309 10.312268 -0.10195753 9.0646825 0.06313182 M 14.235529 6.538212 c 0.719844 -0.1922804 1.369569 0.5544499 0.96037 1.2142088 -0.345429 0.5568703 -0.967577 1.0212266 -1.430447 1.4820746 L 10.94499 12.042639 C 10.500924 12.484766 9.9264114 13.323465 9.299721 13.490862 8.8023811 13.623702 8.452016 13.299829 8.1245295 12.978374 7.342478 12.210582 6.3754514 11.44552 5.7298007 10.560564 5.480503 10.218905 5.4699265 9.723192 5.7920077 9.4212362 6.6694846 8.5988409 7.8158456 10.253773 8.3595682 10.794576 c 0.1820751 0.181125 0.4825335 0.608587 0.7834627 0.52988 0.4212659 -0.110141 0.8750481 -0.777076 1.1751921 -1.075907 L 12.981992 7.5964118 C 13.331649 7.2482817 13.738346 6.6710512 14.235529 6.538212 Z"
|
||||||
|
FillRule="NonZero"
|
||||||
|
/>
|
||||||
|
<PathGeometry x:Key="CircledX" Figures="M 9.3972738 0.04245969 C 7.0827527 0.34574986 5.0318949 1.0076069 3.2592046 2.6077382 2.3324496 3.4442761 1.5788823 4.453119 1.0210803 5.566508 c -2.7620688 5.513177 0.320857 12.50987 6.3432023 14.090152 1.2916144 0.338914 2.6250608 0.43131 3.9486944 0.2576 4.747034 -0.622966 8.468465 -4.700542 8.677783 -9.470458 C 20.19922 5.6929665 16.858078 1.3284705 12.212185 0.27365016 11.325097 0.07224603 10.306294 -0.0766601 9.3972738 0.04245969 M 6.8951311 6.0962212 c 0.4071071 0.00285 0.6713562 0.2964224 0.938304 0.5628006 l 1.4856467 1.4826978 c 0.158768 0.1584535 0.4033136 0.5189835 0.6255352 0.5771984 0.126202 0.033088 0.234732 -0.1102653 0.312768 -0.1870931 0.249277 -0.2453478 0.495229 -0.4941674 0.742824 -0.7412705 l 1.094688 -1.0925142 c 0.20842 -0.2080069 0.414066 -0.4608068 0.703728 -0.554646 0.666547 -0.2158494 1.280275 0.3966607 1.063998 1.0618847 -0.141137 0.4339627 -0.633395 0.779821 -0.94671 1.092514 L 11.664841 9.54638 c -0.107944 0.1077293 -0.38877 0.3007922 -0.38877 0.46822 0 0.222015 0.475094 0.554373 0.623346 0.70233 0.595784 0.594602 1.420358 1.20083 1.871407 1.9119 0.241614 0.380976 0.158691 0.920717 -0.229532 1.171722 -0.308154 0.199227 -0.730468 0.156386 -1.016338 -0.06095 C 11.870017 13.24165 11.308677 12.546499 10.726537 11.965515 10.582664 11.821928 10.166604 11.252104 9.944617 11.31028 9.7223954 11.3685 9.4778498 11.729025 9.3190818 11.887479 l -1.4856467 1.4827 C 7.520315 13.682638 7.2187683 13.989088 6.7387474 13.917412 6.1917559 13.835786 5.9203119 13.23264 6.1340103 12.745886 6.2635359 12.450907 6.5544494 12.227488 6.7778435 12.004537 L 8.0289153 10.755949 C 8.183657 10.601515 8.691357 10.24512 8.691357 10.0146 c 0 -0.2305204 -0.5077 -0.5869139 -0.6624417 -0.7413489 L 6.7778435 8.0246649 C 6.5544494 7.8017132 6.2635359 7.5782948 6.1340103 7.2833157 5.8906767 6.7289817 6.2771407 6.091851 6.8951311 6.0962212 Z" FillRule="NonZero"
|
||||||
|
/>
|
||||||
|
<PathGeometry x:Key="CircledWarn" Figures="M 9.4328769 0.04019892 C 7.0982838 0.34605265 4.9864964 0.9947734 3.2144923 2.6416847 -0.51716071 6.1098902 -1.0931625 11.937378 1.9776592 16.023146 c 0.6666064 0.886919 1.4893703 1.657003 2.4101133 2.276223 0.9414784 0.633151 1.9874973 1.110834 3.0896371 1.387001 1.2547837 0.314488 2.5508664 0.396281 3.8327134 0.22809 4.776463 -0.626673 8.470809 -4.727503 8.680748 -9.510654 C 20.200809 5.6204614 16.766697 1.2560532 12.09231 0.24958776 11.252125 0.06868514 10.291794 -0.07233093 9.4328769 0.04019892 M 9.784861 4.2119583 c 0.934085 -0.1667851 1.016841 0.6682722 1.016841 1.352973 v 5.1120367 c 0 0.62605 0.190424 1.671637 -0.664858 1.821253 C 9.2424915 12.654703 9.1591106 11.781677 9.1591106 11.145248 V 6.0332096 c 0 -0.6032595 -0.201139 -1.6735876 0.6257504 -1.8212513 m 0 9.9899357 c 1.062442 -0.189692 1.447474 1.424659 0.391092 1.613298 -1.0624433 0.189692 -1.4474743 -1.424659 -0.391092 -1.613298 z" FillRule="NonZero"
|
||||||
|
/>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
29
SPTInstaller/App.axaml.cs
Normal file
29
SPTInstaller/App.axaml.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using SPTInstaller.ViewModels;
|
||||||
|
using SPTInstaller.Views;
|
||||||
|
|
||||||
|
namespace SPTInstaller
|
||||||
|
{
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow
|
||||||
|
{
|
||||||
|
DataContext = new MainWindowViewModel(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
187
SPTInstaller/Assets/Styles.axaml
Normal file
187
SPTInstaller/Assets/Styles.axaml
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:cc="using:SPTInstaller.CustomControls"
|
||||||
|
xmlns:rxui="using:Avalonia.ReactiveUI"
|
||||||
|
>
|
||||||
|
<Design.PreviewWith>
|
||||||
|
<StackPanel Spacing="5" Background="{StaticResource AKI_Background_Dark}">
|
||||||
|
<Button Content="Blah"/>
|
||||||
|
<TextBox Text="Some cool text here" Margin="5"/>
|
||||||
|
<TextBox Watermark="This is a watermark" Margin="5"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Design.PreviewWith>
|
||||||
|
|
||||||
|
<!-- Add Styles Here -->
|
||||||
|
|
||||||
|
<!-- TitleBar Styles -->
|
||||||
|
<Style Selector="cc|TitleBar">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||||
|
<Setter Property="ButtonForeground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="cc|TitleBar.versiontag">
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0 0 0 2"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- TextBox Styles -->
|
||||||
|
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml -->
|
||||||
|
<Style Selector="TextBox">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox:focus">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox:pointerover">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox:pointerover /template/ Border#PART_BorderElement">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="DimGray"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox:pointerover /template/ TextBlock#PART_Watermark, TextBox:focus /template/ TextBlock#PART_FloatingWatermark">
|
||||||
|
<Setter Property="Foreground" Value="DimGray"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox:focus /template/ TextBlock#PART_Watermark, TextBox:focus /template/ TextBlock#PART_FloatingWatermark">
|
||||||
|
<Setter Property="Foreground" Value="DimGray"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox /template/ TextBlock#PART_Watermark, TextBox:focus /template/ TextBlock#PART_FloatingWatermark">
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Label Styles -->
|
||||||
|
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Label.xaml -->
|
||||||
|
<Style Selector="Label">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Label.yellow">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Label.dark">
|
||||||
|
<Setter Property="Foreground" Value="DimGray"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Label.versionMismatch">
|
||||||
|
<Setter Property="Foreground" Value="OrangeRed"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ProgressBar Styles -->
|
||||||
|
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml -->
|
||||||
|
<Style Selector="ProgressBar">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="ProgressBar.error">
|
||||||
|
<Setter Property="Foreground" Value="Red"/>
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration="0:0:0.5" FillMode="Forward">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
<Setter Property="Value" Value="0"/>
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="Foreground" Value="Red"/>
|
||||||
|
<Setter Property="Value" Value="100"/>
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Seperator Styles -->
|
||||||
|
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Separator.xaml -->
|
||||||
|
<Style Selector="Separator">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Background_Dark}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Button Styles -->
|
||||||
|
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Button.xaml -->
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button:pointerover">
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button:disabled /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Button yellow -->
|
||||||
|
<Style Selector="Button.yellow">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.yellow:pointerover">
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.yellow:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="Gold"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.yellow:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.yellow:disabled /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Button Link Style -->
|
||||||
|
<Style Selector="Button.link">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.link: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"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.link: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>
|
||||||
|
</Styles>
|
BIN
SPTInstaller/Assets/icon.ico
Normal file
BIN
SPTInstaller/Assets/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
20
SPTInstaller/Behaviors/SpanBehavior.cs
Normal file
20
SPTInstaller/Behaviors/SpanBehavior.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Behaviors
|
||||||
|
{
|
||||||
|
public class SpanBehavior : AvaloniaObject
|
||||||
|
{
|
||||||
|
public static readonly AttachedProperty<bool> SpanProperty = AvaloniaProperty.RegisterAttached<SpanBehavior, Interactive, bool>("Span");
|
||||||
|
|
||||||
|
public static void SetSpan(AvaloniaObject element, bool value)
|
||||||
|
{
|
||||||
|
element.SetValue(SpanProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetSpan(AvaloniaObject element)
|
||||||
|
{
|
||||||
|
return element.GetValue(SpanProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
SPTInstaller/Controllers/InstallController.cs
Normal file
62
SPTInstaller/Controllers/InstallController.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using SharpCompress;
|
||||||
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Controllers
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_tasks = tasks;
|
||||||
|
_preChecks = preChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IResult> RunPreChecks()
|
||||||
|
{
|
||||||
|
var requiredResults = new List<IResult>();
|
||||||
|
|
||||||
|
_preChecks.ForEach(x => x.IsPending = true);
|
||||||
|
|
||||||
|
foreach (var check in _preChecks)
|
||||||
|
{
|
||||||
|
var result = await check.RunCheck();
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
foreach (var task in _tasks)
|
||||||
|
{
|
||||||
|
TaskChanged?.Invoke(null, task);
|
||||||
|
|
||||||
|
var result = await task.RunAsync();
|
||||||
|
|
||||||
|
if (!result.Succeeded) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.FromSuccess("Install Complete. Happy Playing!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
SPTInstaller/Converters/InvertedProgressConverter.cs
Normal file
29
SPTInstaller/Converters/InvertedProgressConverter.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Converters
|
||||||
|
{
|
||||||
|
public class InvertedProgressConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if( value is int progress)
|
||||||
|
{
|
||||||
|
return 100 - progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if ( value is int invertedProgress)
|
||||||
|
{
|
||||||
|
return 100 - invertedProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
SPTInstaller/CustomControls/DistributedSpacePanel.cs
Normal file
77
SPTInstaller/CustomControls/DistributedSpacePanel.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using SPTInstaller.Behaviors;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace SPTInstaller.CustomControls
|
||||||
|
{
|
||||||
|
public class DistributedSpacePanel : Panel
|
||||||
|
{
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
|
{
|
||||||
|
var children = Children;
|
||||||
|
|
||||||
|
for (int i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
// measure child objects so we can use their desired size in the arrange override
|
||||||
|
var child = children[i];
|
||||||
|
child.Measure(availableSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we want to use all available space
|
||||||
|
return availableSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 child = children[i];
|
||||||
|
|
||||||
|
var spanChild = child.GetValue(SpanBehavior.SpanProperty);
|
||||||
|
|
||||||
|
if (spanChild)
|
||||||
|
{
|
||||||
|
// move any spanning children to the top of the array to push them behind the other controls (visually)
|
||||||
|
children.Move(i, 0);
|
||||||
|
|
||||||
|
rcChild = rcChild.WithY(0)
|
||||||
|
.WithX(0)
|
||||||
|
.WithHeight(finalSize.Height)
|
||||||
|
.WithWidth(finalSize.Width);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
SPTInstaller/CustomControls/PreCheckItem.axaml
Normal file
84
SPTInstaller/CustomControls/PreCheckItem.axaml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<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.PreCheckItem">
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<Grid 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>
|
||||||
|
</UserControl>
|
62
SPTInstaller/CustomControls/PreCheckItem.axaml.cs
Normal file
62
SPTInstaller/CustomControls/PreCheckItem.axaml.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Shapes;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using ReactiveUI;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace SPTInstaller.CustomControls
|
||||||
|
{
|
||||||
|
public partial class PreCheckItem : UserControl
|
||||||
|
{
|
||||||
|
public PreCheckItem()
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
79
SPTInstaller/CustomControls/ProgressableTaskItem.axaml
Normal file
79
SPTInstaller/CustomControls/ProgressableTaskItem.axaml
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<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:vm="using:SPTInstaller.ViewModels"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="SPTInstaller.CustomControls.ProgressableTaskItem">
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<!-- Ellipse Styles -->
|
||||||
|
<Style Selector="Ellipse">
|
||||||
|
<Setter Property="Stroke" Value="{Binding PendingColor, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
|
<Setter Property="Margin" Value="7 0"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Ellipse.completed">
|
||||||
|
<Setter Property="Stroke" Value="{Binding CompletedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Ellipse.running">
|
||||||
|
<Setter Property="Stroke" Value="{Binding RunningColor, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
|
<Setter Property="Margin" Value="7 3"/>
|
||||||
|
<Style.Animations>
|
||||||
|
<Animation Duration="0:0:1" PlaybackDirection="Alternate" RepeatCount="INFINITE">
|
||||||
|
<KeyFrame Cue="0%">
|
||||||
|
<Setter Property="ScaleTransform.ScaleX" Value="1"/>
|
||||||
|
<Setter Property="ScaleTransform.ScaleY" Value="1"/>
|
||||||
|
</KeyFrame>
|
||||||
|
<KeyFrame Cue="100%">
|
||||||
|
<Setter Property="ScaleTransform.ScaleX" Value="1.2"/>
|
||||||
|
<Setter Property="ScaleTransform.ScaleY" Value="1.2"/>
|
||||||
|
</KeyFrame>
|
||||||
|
</Animation>
|
||||||
|
</Style.Animations>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Ellipse.centerRunning">
|
||||||
|
<Setter Property="Fill" Value="{Binding RunningColor, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Ellipse.centerCompleted">
|
||||||
|
<Setter Property="Fill" Value="{Binding CompletedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Label Styles -->
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{Binding PendingColor, RelativeSource={RelativeSource
|
||||||
|
AncestorType=UserControl}}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.completed">
|
||||||
|
<Setter Property="Foreground" Value="{Binding CompletedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock.running">
|
||||||
|
<Setter Property="Foreground" Value="{Binding RunningColor, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<Grid ColumnDefinitions="AUTO, *">
|
||||||
|
|
||||||
|
<Ellipse Height="30" Width="30"
|
||||||
|
StrokeThickness="4"
|
||||||
|
Fill="{StaticResource AKI_Background_Dark}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Classes.running="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
Classes.completed="{Binding IsCompleted, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Ellipse Height="20" Width="20"
|
||||||
|
Classes.centerRunning="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
Classes.centerCompleted="{Binding IsCompleted, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Text="{Binding TaskName, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
Classes.running="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
Classes.completed="{Binding IsCompleted, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
FontSize="15"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
77
SPTInstaller/CustomControls/ProgressableTaskItem.axaml.cs
Normal file
77
SPTInstaller/CustomControls/ProgressableTaskItem.axaml.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
|
||||||
|
namespace SPTInstaller.CustomControls
|
||||||
|
{
|
||||||
|
public partial class ProgressableTaskItem : UserControl
|
||||||
|
{
|
||||||
|
public ProgressableTaskItem()
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
47
SPTInstaller/CustomControls/ProgressableTaskList.axaml
Normal file
47
SPTInstaller/CustomControls/ProgressableTaskList.axaml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<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"
|
||||||
|
xmlns:bh="using:SPTInstaller.Behaviors"
|
||||||
|
xmlns:convt="using:SPTInstaller.Converters"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="SPTInstaller.CustomControls.ProgressableTaskList"
|
||||||
|
>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<convt:InvertedProgressConverter x:Key="invtProgressConvt"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
<ItemsControl Name="itemsControl" Items="{Binding Tasks, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
Padding="5">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<cc:DistributedSpacePanel>
|
||||||
|
<ProgressBar Orientation="Vertical"
|
||||||
|
Value="{Binding TaskProgress, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource ResourceKey=invtProgressConvt}}"
|
||||||
|
Background="{Binding CompletedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
Foreground="{Binding PendingColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Margin="20 3"
|
||||||
|
MinHeight="0"
|
||||||
|
Grid.RowSpan="6"
|
||||||
|
bh:SpanBehavior.Span="True"
|
||||||
|
|
||||||
|
/>
|
||||||
|
</cc:DistributedSpacePanel>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<cc:ProgressableTaskItem TaskId="{Binding Id}"
|
||||||
|
TaskName="{Binding Name}"
|
||||||
|
IsRunning="{Binding IsRunning}"
|
||||||
|
IsCompleted="{Binding IsCompleted}"
|
||||||
|
PendingColor="{Binding PendingColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
RunningColor="{Binding RunningColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
CompletedColor="{Binding CompletedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</UserControl>
|
97
SPTInstaller/CustomControls/ProgressableTaskList.axaml.cs
Normal file
97
SPTInstaller/CustomControls/ProgressableTaskList.axaml.cs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using DynamicData;
|
||||||
|
using DynamicData.Binding;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.CustomControls
|
||||||
|
{
|
||||||
|
public partial class ProgressableTaskList : UserControl
|
||||||
|
{
|
||||||
|
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 () =>
|
||||||
|
{
|
||||||
|
var completedTasks = Tasks.Where(x => x.IsCompleted == true).Count();
|
||||||
|
|
||||||
|
var progress = (int)Math.Floor((double)completedTasks / (Tasks.Count - 1) * 100);
|
||||||
|
|
||||||
|
for(; TaskProgress < progress;)
|
||||||
|
{
|
||||||
|
TaskProgress += 2;
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
39
SPTInstaller/CustomControls/TaskDetails.axaml
Normal file
39
SPTInstaller/CustomControls/TaskDetails.axaml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<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.TaskDetails">
|
||||||
|
<Grid RowDefinitions="10,*,AUTO,10,AUTO,*,AUTO,10" ColumnDefinitions="10,*,10">
|
||||||
|
|
||||||
|
<Label Grid.Column="1" Grid.Row="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="15"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Content="{Binding Message, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Column="1" Grid.Row="4"
|
||||||
|
Foreground="Gainsboro"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
FontSize="12"
|
||||||
|
Text="{Binding Details, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
TextTrimming="CharacterEllipsis"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
MaxLines="3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Grid Grid.Column="1" Grid.Row="6" ColumnDefinitions="*,AUTO">
|
||||||
|
|
||||||
|
<ProgressBar IsVisible="{Binding ShowProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
Value="{Binding Progress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Label Grid.Column="1"
|
||||||
|
Content="{Binding Progress, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat='{}{0}%'}"
|
||||||
|
IsVisible="{Binding ShowProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
49
SPTInstaller/CustomControls/TaskDetails.axaml.cs
Normal file
49
SPTInstaller/CustomControls/TaskDetails.axaml.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace SPTInstaller.CustomControls
|
||||||
|
{
|
||||||
|
public partial class TaskDetails : UserControl
|
||||||
|
{
|
||||||
|
public TaskDetails()
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
76
SPTInstaller/CustomControls/TitleBar.axaml
Normal file
76
SPTInstaller/CustomControls/TitleBar.axaml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<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.TitleBar">
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="AUTO,*,AUTO,AUTO">
|
||||||
|
|
||||||
|
<Rectangle Grid.ColumnSpan="6" IsHitTestVisible="False"
|
||||||
|
Fill="{Binding Background, RelativeSource={
|
||||||
|
RelativeSource AncestorType=UserControl}}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Label Content="{Binding Title, RelativeSource={
|
||||||
|
RelativeSource AncestorType=UserControl}}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Foreground="{Binding Foreground, RelativeSource={
|
||||||
|
RelativeSource AncestorType=UserControl}}"
|
||||||
|
Background="Transparent"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Minimize (-) Button -->
|
||||||
|
<Button Content="" Grid.Column="2"
|
||||||
|
Foreground="{Binding ButtonForeground, RelativeSource={
|
||||||
|
RelativeSource AncestorType=UserControl}}"
|
||||||
|
Command="{Binding MinButtonCommand, RelativeSource={
|
||||||
|
RelativeSource AncestorType=UserControl}}"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
FontFamily="Segoe MDL2 Assets"
|
||||||
|
CornerRadius="0"
|
||||||
|
Width="35"
|
||||||
|
>
|
||||||
|
<Button.Styles>
|
||||||
|
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AKI_Background_Light}"/>
|
||||||
|
</Style>
|
||||||
|
</Button.Styles>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<!-- Close (X) Button -->
|
||||||
|
<Button Content="" Grid.Column="3"
|
||||||
|
Foreground="{Binding ButtonForeground, RelativeSource={
|
||||||
|
RelativeSource AncestorType=UserControl}}"
|
||||||
|
Command="{Binding XButtonCommand, RelativeSource={
|
||||||
|
RelativeSource AncestorType=UserControl}}"
|
||||||
|
Background="Transparent"
|
||||||
|
HorizontalContentAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
FontFamily="Segoe MDL2 Assets"
|
||||||
|
CornerRadius="0"
|
||||||
|
Width="35"
|
||||||
|
>
|
||||||
|
<Button.Styles>
|
||||||
|
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="IndianRed"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="Crimson"/>
|
||||||
|
</Style>
|
||||||
|
</Button.Styles>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
77
SPTInstaller/CustomControls/TitleBar.axaml.cs
Normal file
77
SPTInstaller/CustomControls/TitleBar.axaml.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace SPTInstaller.CustomControls
|
||||||
|
{
|
||||||
|
public partial class TitleBar : UserControl
|
||||||
|
{
|
||||||
|
public TitleBar()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<string> TitleProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, string>(nameof(Title));
|
||||||
|
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get => GetValue(TitleProperty);
|
||||||
|
set => SetValue(TitleProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> ButtonForegroundProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(ButtonForeground));
|
||||||
|
|
||||||
|
public IBrush ButtonForeground
|
||||||
|
{
|
||||||
|
get => GetValue(ButtonForegroundProperty);
|
||||||
|
set => SetValue(ButtonForegroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static new readonly StyledProperty<IBrush> ForegroundProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Foreground));
|
||||||
|
|
||||||
|
public new IBrush Foreground
|
||||||
|
{
|
||||||
|
get => GetValue(ForegroundProperty);
|
||||||
|
set => SetValue(ForegroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static new readonly StyledProperty<IBrush> BackgroundProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, IBrush>(nameof(Background));
|
||||||
|
|
||||||
|
public new IBrush Background
|
||||||
|
{
|
||||||
|
get => GetValue(BackgroundProperty);
|
||||||
|
set => SetValue(BackgroundProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close Button Command (X Button) Property
|
||||||
|
public static readonly StyledProperty<ICommand> XButtonCommandProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(XButtonCommand));
|
||||||
|
|
||||||
|
public ICommand XButtonCommand
|
||||||
|
{
|
||||||
|
get => GetValue(XButtonCommandProperty);
|
||||||
|
set => SetValue(XButtonCommandProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Minimize Button Command (- Button) Property
|
||||||
|
public static readonly StyledProperty<ICommand> MinButtonCommandProperty =
|
||||||
|
AvaloniaProperty.Register<TitleBar, ICommand>(nameof(MinButtonCommand));
|
||||||
|
|
||||||
|
public ICommand MinButtonCommand
|
||||||
|
{
|
||||||
|
get => GetValue(MinButtonCommandProperty);
|
||||||
|
set => SetValue(MinButtonCommandProperty, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
using HttpClientProgress;
|
using HttpClientProgress;
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Helper
|
namespace SPTInstaller.Aki.Helper
|
||||||
{
|
{
|
||||||
public static class DownloadCacheHelper
|
public static class DownloadCacheHelper
|
||||||
{
|
{
|
||||||
@ -39,7 +39,7 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<GenericResult> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
private static async Task<Result> DownloadFile(FileInfo outputFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -51,27 +51,27 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
|
|
||||||
if (!outputFile.Exists)
|
if (!outputFile.Exists)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError($"Failed to download {outputFile.Name}");
|
return Result.FromError($"Failed to download {outputFile.Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash))
|
if (expectedHash != null && !FileHashHelper.CheckHash(outputFile, expectedHash))
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Hash mismatch");
|
return Result.FromError("Hash mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError(ex.Message);
|
return Result.FromError(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<GenericResult> ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null)
|
private static async Task<Result> ProcessInboundStreamAsync(FileInfo cacheFile, Stream downloadStream, string expectedHash = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (CheckCache(cacheFile, expectedHash)) return GenericResult.FromSuccess();
|
if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
|
||||||
|
|
||||||
using var patcherFileStream = cacheFile.Open(FileMode.Create);
|
using var patcherFileStream = cacheFile.Open(FileMode.Create);
|
||||||
{
|
{
|
||||||
@ -82,32 +82,32 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
|
|
||||||
if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash))
|
if (expectedHash != null && !FileHashHelper.CheckHash(cacheFile, expectedHash))
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Hash mismatch");
|
return Result.FromError("Hash mismatch");
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError(ex.Message);
|
return Result.FromError(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<GenericResult> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
private static async Task<Result> ProcessInboundFileAsync(FileInfo cacheFile, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (CheckCache(cacheFile, expectedHash)) return GenericResult.FromSuccess();
|
if (CheckCache(cacheFile, expectedHash)) return Result.FromSuccess();
|
||||||
|
|
||||||
return await DownloadFile(cacheFile, targetLink, progress, expectedHash);
|
return await DownloadFile(cacheFile, targetLink, progress, expectedHash);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError(ex.Message);
|
return Result.FromError(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<FileInfo> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash = null)
|
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash = null)
|
||||||
{
|
{
|
||||||
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
||||||
|
|
||||||
@ -123,7 +123,7 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<FileInfo> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null)
|
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash = null)
|
||||||
{
|
{
|
||||||
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
FileInfo cacheFile = new FileInfo(Path.Join(_cachePath, fileName));
|
||||||
|
|
@ -5,7 +5,7 @@ using System.Linq;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Helper
|
namespace SPTInstaller.Aki.Helper
|
||||||
{
|
{
|
||||||
public static class FileHashHelper
|
public static class FileHashHelper
|
||||||
{
|
{
|
72
SPTInstaller/Helpers/FileHelper.cs
Normal file
72
SPTInstaller/Helpers/FileHelper.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Aki.Helper
|
||||||
|
{
|
||||||
|
public static class FileHelper
|
||||||
|
{
|
||||||
|
private static Result IterateDirectories(DirectoryInfo sourceDir, DirectoryInfo targetDir)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var dir in sourceDir.GetDirectories("*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dir.FullName.Replace(sourceDir.FullName, targetDir.FullName));
|
||||||
|
}
|
||||||
|
return Result.FromSuccess();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.FromError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action<string, int> updateCallback = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length;
|
||||||
|
int processedFiles = 0;
|
||||||
|
|
||||||
|
foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
updateCallback?.Invoke(file.Name, (int)Math.Floor(((double)processedFiles / totalFiles) * 100));
|
||||||
|
|
||||||
|
File.Copy(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName), true);
|
||||||
|
processedFiles++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.FromSuccess();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result.FromError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return Result.FromError(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Helper
|
namespace SPTInstaller.Aki.Helper
|
||||||
{
|
{
|
||||||
public static class PreCheckHelper
|
public static class PreCheckHelper
|
||||||
{
|
{
|
||||||
@ -24,16 +24,16 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
return info?.DirectoryName;
|
return info?.DirectoryName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GenericResult DetectOriginalGameVersion(string gamePath)
|
public static Result DetectOriginalGameVersion(string gamePath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string version = FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2];
|
string version = FileVersionInfo.GetVersionInfo(Path.Join(gamePath + "/EscapeFromTarkov.exe")).ProductVersion.Replace('-', '.').Split('.')[^2];
|
||||||
return GenericResult.FromSuccess(version);
|
return Result.FromSuccess(version);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError($"File not found: {ex.Message}");
|
return Result.FromError($"File not found: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Models;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Helper
|
namespace SPTInstaller.Aki.Helper
|
||||||
{
|
{
|
||||||
public enum PatcherExitCode
|
public enum PatcherExitCode
|
||||||
{
|
{
|
||||||
@ -17,11 +17,11 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
|
|
||||||
public static class ProcessHelper
|
public static class ProcessHelper
|
||||||
{
|
{
|
||||||
public static GenericResult PatchClientFiles(FileInfo executable, DirectoryInfo workingDir)
|
public static Result PatchClientFiles(FileInfo executable, DirectoryInfo workingDir)
|
||||||
{
|
{
|
||||||
if (!executable.Exists || !workingDir.Exists)
|
if (!executable.Exists || !workingDir.Exists)
|
||||||
{
|
{
|
||||||
return GenericResult.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();
|
||||||
@ -36,25 +36,25 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
switch ((PatcherExitCode)process.ExitCode)
|
switch ((PatcherExitCode)process.ExitCode)
|
||||||
{
|
{
|
||||||
case PatcherExitCode.Success:
|
case PatcherExitCode.Success:
|
||||||
return GenericResult.FromSuccess("Patcher Finished Successfully, extracting Aki");
|
return Result.FromSuccess("Patcher Finished Successfully, extracting Aki");
|
||||||
|
|
||||||
case PatcherExitCode.ProgramClosed:
|
case PatcherExitCode.ProgramClosed:
|
||||||
return GenericResult.FromError("Patcher was closed before completing!");
|
return Result.FromError("Patcher was closed before completing!");
|
||||||
|
|
||||||
case PatcherExitCode.EftExeNotFound:
|
case PatcherExitCode.EftExeNotFound:
|
||||||
return GenericResult.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 GenericResult.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 GenericResult.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 GenericResult.FromError("A patch failed to apply");
|
return Result.FromError("A patch failed to apply");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return GenericResult.FromError("an unknown error occurred in the patcher");
|
return Result.FromError("an unknown error occurred in the patcher");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
116
SPTInstaller/Helpers/ServiceHelper.cs
Normal file
116
SPTInstaller/Helpers/ServiceHelper.cs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
using Splat;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
private static bool TryRegisterInstance<T, T2>(object[] parameters = null)
|
||||||
|
{
|
||||||
|
var instance = Activator.CreateInstance(typeof(T2), parameters);
|
||||||
|
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
Locator.CurrentMutable.RegisterConstant<T>((T)instance);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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>
|
||||||
|
/// 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 parmesan = constructor.GetParameters();
|
||||||
|
|
||||||
|
if(parmesan.Length == 0)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
throw new InvalidOperationException($"Could not locate service of type '{type.Name}'");
|
||||||
|
|
||||||
|
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)
|
||||||
|
throw new InvalidOperationException($"Could not locate service of type '{nameof(T)}'");
|
||||||
|
|
||||||
|
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)
|
||||||
|
throw new InvalidOperationException($"Could not locate service of type '{nameof(T)}'");
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Helper
|
namespace SPTInstaller.Aki.Helper
|
||||||
{
|
{
|
||||||
public static class ZipHelper
|
public static class ZipHelper
|
||||||
{
|
{
|
||||||
public static GenericResult Decompress(FileInfo ArchivePath, DirectoryInfo OutputFolderPath, IProgress<double> progress = null)
|
public static Result Decompress(FileInfo ArchivePath, DirectoryInfo OutputFolderPath, IProgress<double> progress = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -42,14 +42,14 @@ namespace SPT_AKI_Installer.Aki.Helper
|
|||||||
|
|
||||||
if (!OutputFolderPath.Exists)
|
if (!OutputFolderPath.Exists)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError($"Failed to extract files: {ArchivePath.Name}");
|
return Result.FromError($"Failed to extract files: {ArchivePath.Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError(ex.Message);
|
return Result.FromError(ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,30 +1,29 @@
|
|||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Aki.Helper;
|
||||||
using SPT_AKI_Installer.Aki.Helper;
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
namespace SPTInstaller.Installer_Tasks
|
||||||
{
|
{
|
||||||
public class CopyClientTask : LiveTableTask
|
public class CopyClientTask : InstallerTaskBase
|
||||||
{
|
{
|
||||||
private InternalData _data;
|
private InternalData _data;
|
||||||
|
|
||||||
public CopyClientTask(InternalData data) : base("Copy Client Files", false)
|
public CopyClientTask(InternalData data) : base("Copy Client Files")
|
||||||
{
|
{
|
||||||
_data = data;
|
_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GenericResult> RunAsync()
|
public override async Task<IResult> TaskOperation()
|
||||||
{
|
{
|
||||||
SetStatus("Copying", false);
|
SetStatus("Copying Client Files", 0);
|
||||||
|
|
||||||
var originalGameDirInfo = new DirectoryInfo(_data.OriginalGamePath);
|
var originalGameDirInfo = new DirectoryInfo(_data.OriginalGamePath);
|
||||||
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
||||||
|
|
||||||
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo, (message, progress) => { SetStatus($"Copying Client Files", message, progress); });
|
||||||
|
|
||||||
return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo, progress);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,34 +1,33 @@
|
|||||||
using CG.Web.MegaApiClient;
|
using CG.Web.MegaApiClient;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Aki.Helper;
|
||||||
using SPT_AKI_Installer.Aki.Helper;
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
namespace SPTInstaller.Installer_Tasks
|
||||||
{
|
{
|
||||||
public class DownloadTask : LiveTableTask
|
public class DownloadTask : InstallerTaskBase
|
||||||
{
|
{
|
||||||
private InternalData _data;
|
private InternalData _data;
|
||||||
|
|
||||||
public DownloadTask(InternalData data) : base("Download Files", false)
|
public DownloadTask(InternalData data) : base("Download Files")
|
||||||
{
|
{
|
||||||
_data = data;
|
_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GenericResult> BuildMirrorList()
|
private async Task<IResult> BuildMirrorList()
|
||||||
{
|
{
|
||||||
SetStatus("Downloading Mirror List", false);
|
var progress = new Progress<double>((d) => { SetStatus("Downloading Mirror List", (int)Math.Floor(d));});
|
||||||
|
|
||||||
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
|
||||||
|
|
||||||
var file = await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
|
var file = await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
|
||||||
|
|
||||||
if (file == null)
|
if (file == null)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Failed to download mirror list");
|
return Result.FromError("Failed to download mirror list");
|
||||||
}
|
}
|
||||||
|
|
||||||
var mirrorsList = JsonConvert.DeserializeObject<List<DownloadMirror>>(File.ReadAllText(file.FullName));
|
var mirrorsList = JsonConvert.DeserializeObject<List<DownloadMirror>>(File.ReadAllText(file.FullName));
|
||||||
@ -37,17 +36,17 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
{
|
{
|
||||||
_data.PatcherReleaseMirrors = mirrors;
|
_data.PatcherReleaseMirrors = mirrors;
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromError("Failed to deserialize mirrors list");
|
return Result.FromError("Failed to deserialize mirrors list");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<GenericResult> DownloadPatcherFromMirrors(IProgress<double> progress)
|
private async Task<IResult> DownloadPatcherFromMirrors(IProgress<double> progress)
|
||||||
{
|
{
|
||||||
foreach (var mirror in _data.PatcherReleaseMirrors)
|
foreach (var mirror in _data.PatcherReleaseMirrors)
|
||||||
{
|
{
|
||||||
SetStatus($"Downloading Patcher: {mirror.Link}", false);
|
SetStatus($"Downloading Patcher: {mirror.Link}");
|
||||||
|
|
||||||
// mega is a little weird since they use encryption, but thankfully there is a great library for their api :)
|
// 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"))
|
if (mirror.Link.StartsWith("https://mega"))
|
||||||
@ -69,7 +68,7 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -82,15 +81,17 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
if (_data.PatcherZipInfo != null)
|
if (_data.PatcherZipInfo != null)
|
||||||
{
|
{
|
||||||
return GenericResult.FromSuccess();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromError("Failed to download Patcher");
|
return Result.FromError("Failed to download Patcher");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GenericResult> RunAsync()
|
public override async Task<IResult> TaskOperation()
|
||||||
{
|
{
|
||||||
|
var progress = new Progress<double>((d) => { SetStatus("", (int)Math.Floor(d)); });
|
||||||
|
|
||||||
if (_data.PatchNeeded)
|
if (_data.PatchNeeded)
|
||||||
{
|
{
|
||||||
var buildResult = await BuildMirrorList();
|
var buildResult = await BuildMirrorList();
|
||||||
@ -100,9 +101,8 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
return buildResult;
|
return buildResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
Progress = 0;
|
SetStatus("", 0);
|
||||||
|
|
||||||
var progress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
|
||||||
var patcherDownloadRresult = await DownloadPatcherFromMirrors(progress);
|
var patcherDownloadRresult = await DownloadPatcherFromMirrors(progress);
|
||||||
|
|
||||||
if (!patcherDownloadRresult.Succeeded)
|
if (!patcherDownloadRresult.Succeeded)
|
||||||
@ -111,20 +111,16 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetStatus("Downloading SPT-AKI", false);
|
SetStatus("Downloading SPT-AKI", 0);
|
||||||
|
|
||||||
Progress = 0;
|
_data.AkiZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("sptaki.zip", _data.AkiReleaseDownloadLink, progress, _data.AkiReleaseHash);
|
||||||
|
|
||||||
var akiProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
|
||||||
|
|
||||||
_data.AkiZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("sptaki.zip", _data.AkiReleaseDownloadLink, akiProgress, _data.AkiReleaseHash);
|
|
||||||
|
|
||||||
if (_data.AkiZipInfo == null)
|
if (_data.AkiZipInfo == null)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Failed to download spt-aki");
|
return Result.FromError("Failed to download spt-aki");
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess();
|
return Result.FromSuccess();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,12 @@
|
|||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Aki.Helper;
|
||||||
using SPT_AKI_Installer.Aki.Helper;
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
namespace SPTInstaller.Installer_Tasks
|
||||||
{
|
{
|
||||||
public class InitializationTask : LiveTableTask
|
public class InitializationTask : InstallerTaskBase
|
||||||
{
|
{
|
||||||
private InternalData _data;
|
private InternalData _data;
|
||||||
|
|
||||||
@ -14,13 +15,13 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
_data = data;
|
_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GenericResult> RunAsync()
|
public override async Task<IResult> TaskOperation()
|
||||||
{
|
{
|
||||||
_data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
_data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
||||||
|
|
||||||
if (_data.OriginalGamePath == null)
|
if (_data.OriginalGamePath == null)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("EFT IS NOT INSTALLED!");
|
return Result.FromError("EFT IS NOT INSTALLED!");
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath);
|
var result = PreCheckHelper.DetectOriginalGameVersion(_data.OriginalGamePath);
|
||||||
@ -34,21 +35,21 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
if (_data.OriginalGamePath == null)
|
if (_data.OriginalGamePath == null)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Unable to find EFT OG directory, please make sure EFT is installed. Please also run EFT once");
|
return Result.FromError("Unable to find EFT OG directory, please make sure EFT is installed. Please also run EFT once");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_data.OriginalGamePath == _data.TargetInstallPath)
|
if (_data.OriginalGamePath == _data.TargetInstallPath)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide");
|
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")))
|
if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe")))
|
||||||
{
|
{
|
||||||
return GenericResult.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.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 GenericResult.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}");
|
return Result.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
58
SPTInstaller/Installer Tasks/PreChecks/NetCore6PreCheck.cs
Normal file
58
SPTInstaller/Installer Tasks/PreChecks/NetCore6PreCheck.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Installer_Tasks.PreChecks
|
||||||
|
{
|
||||||
|
public class NetCore6PreCheck : PreCheckBase
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
using Microsoft.Win32;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Installer_Tasks.PreChecks
|
||||||
|
{
|
||||||
|
public class NetFramework472PreCheck : PreCheckBase
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// TODO: log exceptions
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,14 @@
|
|||||||
using Gitea.Api;
|
using Gitea.Api;
|
||||||
using Gitea.Client;
|
using Gitea.Client;
|
||||||
using Gitea.Model;
|
using SPTInstaller.Aki.Helper;
|
||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Interfaces;
|
||||||
using SPT_AKI_Installer.Aki.Helper;
|
using SPTInstaller.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
namespace SPTInstaller.Installer_Tasks
|
||||||
{
|
{
|
||||||
public class ReleaseCheckTask : LiveTableTask
|
public class ReleaseCheckTask : InstallerTaskBase
|
||||||
{
|
{
|
||||||
private InternalData _data;
|
private InternalData _data;
|
||||||
|
|
||||||
@ -18,19 +17,19 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
_data = data;
|
_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GenericResult> RunAsync()
|
public override async Task<IResult> TaskOperation()
|
||||||
{
|
{
|
||||||
Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1";
|
|
||||||
|
|
||||||
var repo = new RepositoryApi(Configuration.Default);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SetStatus("Checking SPT Releases", false);
|
Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1";
|
||||||
|
|
||||||
|
var repo = new RepositoryApi(Configuration.Default);
|
||||||
|
|
||||||
|
SetStatus("Checking SPT Releases");
|
||||||
|
|
||||||
var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases");
|
var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases");
|
||||||
|
|
||||||
SetStatus("Checking for Patches", false);
|
SetStatus("Checking for Patches");
|
||||||
|
|
||||||
var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches");
|
var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches");
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
if (IntGameVersion < IntAkiVersion)
|
if (IntGameVersion < IntAkiVersion)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("Your client is outdated. Please update EFT");
|
return Result.FromError("Your client is outdated. Please update EFT");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,7 +63,7 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
if (comparePatchToAki == null && patchNeedCheck)
|
if (comparePatchToAki == null && patchNeedCheck)
|
||||||
{
|
{
|
||||||
return GenericResult.FromError("No patcher available for your version");
|
return Result.FromError("No patcher available for your version");
|
||||||
}
|
}
|
||||||
|
|
||||||
_data.PatchNeeded = patchNeedCheck;
|
_data.PatchNeeded = patchNeedCheck;
|
||||||
@ -76,12 +75,12 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
status += " - Patch Available";
|
status += " - Patch Available";
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess(status);
|
return Result.FromSuccess(status);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
//request failed
|
//request failed
|
||||||
return GenericResult.FromError("Request Failed");
|
return Result.FromError($"Request Failed:\n{ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,22 +1,23 @@
|
|||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using SPTInstaller.Aki.Helper;
|
||||||
using SPT_AKI_Installer.Aki.Helper;
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core.Tasks
|
namespace SPTInstaller.Installer_Tasks
|
||||||
{
|
{
|
||||||
public class SetupClientTask : LiveTableTask
|
public class SetupClientTask : InstallerTaskBase
|
||||||
{
|
{
|
||||||
private InternalData _data;
|
private InternalData _data;
|
||||||
|
|
||||||
public SetupClientTask(InternalData data) : base("Setup Client", false)
|
public SetupClientTask(InternalData data) : base("Setup Client")
|
||||||
{
|
{
|
||||||
_data = data;
|
_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<GenericResult> RunAsync()
|
public override async Task<IResult> TaskOperation()
|
||||||
{
|
{
|
||||||
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
var targetInstallDirInfo = new DirectoryInfo(_data.TargetInstallPath);
|
||||||
|
|
||||||
@ -24,14 +25,15 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
var patcherEXE = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.exe"));
|
var patcherEXE = new FileInfo(Path.Join(_data.TargetInstallPath, "patcher.exe"));
|
||||||
|
|
||||||
|
var progress = new Progress<double>((d) => { SetStatus("", (int)Math.Floor(d)); });
|
||||||
|
|
||||||
|
|
||||||
if (_data.PatchNeeded)
|
if (_data.PatchNeeded)
|
||||||
{
|
{
|
||||||
// extract patcher files
|
// extract patcher files
|
||||||
SetStatus("Extrating Patcher", false);
|
SetStatus("Extrating Patcher", 0);
|
||||||
|
|
||||||
var extractPatcherProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, progress);
|
||||||
|
|
||||||
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, extractPatcherProgress);
|
|
||||||
|
|
||||||
if (!extractPatcherResult.Succeeded)
|
if (!extractPatcherResult.Succeeded)
|
||||||
{
|
{
|
||||||
@ -39,14 +41,11 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
}
|
}
|
||||||
|
|
||||||
// copy patcher files to install directory
|
// copy patcher files to install directory
|
||||||
SetStatus("Copying Patcher", false);
|
SetStatus("Copying Patcher", 0);
|
||||||
|
|
||||||
var patcherDirInfo = patcherOutputDir.GetDirectories("Patcher*", SearchOption.TopDirectoryOnly).First();
|
var patcherDirInfo = patcherOutputDir.GetDirectories("Patcher*", SearchOption.TopDirectoryOnly).First();
|
||||||
|
|
||||||
|
var copyPatcherResult = FileHelper.CopyDirectoryWithProgress(patcherDirInfo, targetInstallDirInfo, progress);
|
||||||
var copyPatcherProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
|
||||||
|
|
||||||
var copyPatcherResult = FileHelper.CopyDirectoryWithProgress(patcherDirInfo, targetInstallDirInfo, copyPatcherProgress);
|
|
||||||
|
|
||||||
if (!copyPatcherResult.Succeeded)
|
if (!copyPatcherResult.Succeeded)
|
||||||
{
|
{
|
||||||
@ -55,7 +54,9 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
// run patcher
|
// run patcher
|
||||||
SetStatus("Running Patcher");
|
SetStatus("Running Patcher");
|
||||||
StartDrawingIndeterminateProgress();
|
|
||||||
|
// TODO: indeterminate progress indicator
|
||||||
|
//StartDrawingIndeterminateProgress();
|
||||||
|
|
||||||
var patchingResult = ProcessHelper.PatchClientFiles(patcherEXE, targetInstallDirInfo);
|
var patchingResult = ProcessHelper.PatchClientFiles(patcherEXE, targetInstallDirInfo);
|
||||||
|
|
||||||
@ -65,15 +66,10 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// extract release files
|
// extract release files
|
||||||
SetStatus("Extracting Release");
|
SetStatus("Extracting Release", 0);
|
||||||
StartDrawingProgress();
|
|
||||||
|
|
||||||
var extractReleaseProgress = new Progress<double>((d) => { Progress = (int)Math.Floor(d); });
|
var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, progress);
|
||||||
|
|
||||||
var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, extractReleaseProgress);
|
|
||||||
|
|
||||||
if (!extractReleaseResult.Succeeded)
|
if (!extractReleaseResult.Succeeded)
|
||||||
{
|
{
|
||||||
@ -82,7 +78,9 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
|
|
||||||
// cleanup temp files
|
// cleanup temp files
|
||||||
SetStatus("Cleanup");
|
SetStatus("Cleanup");
|
||||||
StartDrawingIndeterminateProgress();
|
|
||||||
|
// TODO: indeterminate progress indicator
|
||||||
|
//StartDrawingIndeterminateProgress();
|
||||||
|
|
||||||
if(_data.PatchNeeded)
|
if(_data.PatchNeeded)
|
||||||
{
|
{
|
||||||
@ -90,7 +88,7 @@ namespace SPT_AKI_Installer.Aki.Core.Tasks
|
|||||||
patcherEXE.Delete();
|
patcherEXE.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GenericResult.FromSuccess("SPT is Setup. Happy Playing!");
|
return Result.FromSuccess("SPT is Setup. Happy Playing!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
35
SPTInstaller/Installer Tasks/TestTask.cs
Normal file
35
SPTInstaller/Installer Tasks/TestTask.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Installer_Tasks
|
||||||
|
{
|
||||||
|
internal class TestTask : InstallerTaskBase
|
||||||
|
{
|
||||||
|
public static TestTask FromRandomName() => new TestTask($"Test Task #{new Random().Next(0, 9999)}");
|
||||||
|
|
||||||
|
public TestTask(string name) : base(name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async override Task<IResult> TaskOperation()
|
||||||
|
{
|
||||||
|
int total = 4;
|
||||||
|
TimeSpan interval = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
for(int i = 0; i < total; i++)
|
||||||
|
{
|
||||||
|
var count = i + 1;
|
||||||
|
string progressMessage = $"Running Task: {Name}";
|
||||||
|
int progress = (int)Math.Floor((double)count / total * 100);
|
||||||
|
|
||||||
|
SetStatus(progressMessage, $"Details: ({count}/{total})", progress);
|
||||||
|
|
||||||
|
await Task.Delay(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.FromSuccess();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
SPTInstaller/Interfaces/IPreCheck.cs
Normal file
17
SPTInstaller/Interfaces/IPreCheck.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Interfaces
|
||||||
|
{
|
||||||
|
public interface IPreCheck
|
||||||
|
{
|
||||||
|
public string Id { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public bool IsRequired { get; }
|
||||||
|
|
||||||
|
public bool IsPending { get; set; }
|
||||||
|
|
||||||
|
public bool Passed { get; }
|
||||||
|
|
||||||
|
public Task<IResult> RunCheck();
|
||||||
|
}
|
||||||
|
}
|
24
SPTInstaller/Interfaces/IProgressableTask.cs
Normal file
24
SPTInstaller/Interfaces/IProgressableTask.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Interfaces
|
||||||
|
{
|
||||||
|
public interface IProgressableTask
|
||||||
|
{
|
||||||
|
public string Id { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public bool IsCompleted { get; }
|
||||||
|
|
||||||
|
public bool HasErrors { get; }
|
||||||
|
|
||||||
|
public bool IsRunning { get; }
|
||||||
|
|
||||||
|
public string StatusMessage { get; }
|
||||||
|
|
||||||
|
public int Progress { get; }
|
||||||
|
|
||||||
|
public bool ShowProgress { get; }
|
||||||
|
|
||||||
|
public Task<IResult> RunAsync();
|
||||||
|
}
|
||||||
|
}
|
14
SPTInstaller/Interfaces/IResult.cs
Normal file
14
SPTInstaller/Interfaces/IResult.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Interfaces
|
||||||
|
{
|
||||||
|
public interface IResult
|
||||||
|
{
|
||||||
|
public bool Succeeded { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
namespace SPT_AKI_Installer.Aki.Core.Model
|
namespace SPTInstaller.Models
|
||||||
{
|
{
|
||||||
public class DownloadMirror
|
public class DownloadMirror
|
||||||
{
|
{
|
134
SPTInstaller/Models/InstallerTaskBase.cs
Normal file
134
SPTInstaller/Models/InstallerTaskBase.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
using Avalonia.Threading;
|
||||||
|
using ReactiveUI;
|
||||||
|
using SPTInstaller.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Models
|
||||||
|
{
|
||||||
|
public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||||
|
{
|
||||||
|
private string _id;
|
||||||
|
public string Id
|
||||||
|
{
|
||||||
|
get => _id;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _name;
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isComleted;
|
||||||
|
public bool IsCompleted
|
||||||
|
{
|
||||||
|
get => _isComleted;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _isComleted, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _hasErrors;
|
||||||
|
public bool HasErrors
|
||||||
|
{
|
||||||
|
get => _hasErrors;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _hasErrors, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isRunning;
|
||||||
|
public bool IsRunning
|
||||||
|
{
|
||||||
|
get => _isRunning;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _isRunning, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _progress;
|
||||||
|
public int Progress
|
||||||
|
{
|
||||||
|
get => _progress;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _progress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _showProgress;
|
||||||
|
public bool ShowProgress
|
||||||
|
{
|
||||||
|
get => _showProgress;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _showProgress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _statusMessage;
|
||||||
|
public string StatusMessage
|
||||||
|
{
|
||||||
|
get => _statusMessage;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _statusDetails;
|
||||||
|
public string StatusDetails
|
||||||
|
{
|
||||||
|
get => _statusDetails;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _statusDetails, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the status message and optionally a progress bar value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <param name="progress"></param>
|
||||||
|
/// <remarks>If message is empty, it isn't updated. If progress is null, the progress bar will be hidden. Details is not touched with this method</remarks>
|
||||||
|
public void SetStatus(string message, int? progress = null) => SetStatus(message, "", progress);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the status message, status details, and optionlly a progress bar value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <param name="progress"></param>
|
||||||
|
/// <remarks>If message or details are empty, it isn't updated. If progress is null, the progress bar will be hidden</remarks>
|
||||||
|
public void SetStatus(string message, string details, int? progress = null)
|
||||||
|
{
|
||||||
|
StatusMessage = String.IsNullOrWhiteSpace(message) ? StatusMessage : message;
|
||||||
|
StatusDetails = String.IsNullOrWhiteSpace(details) ? StatusDetails : details;
|
||||||
|
ShowProgress = progress != null;
|
||||||
|
|
||||||
|
if (progress != null)
|
||||||
|
{
|
||||||
|
Progress = progress.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InstallerTaskBase(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Id = Guid.NewGuid().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
// TODO: handle error state
|
||||||
|
}
|
||||||
|
|
||||||
|
IsCompleted = true;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The task you want to run
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public abstract Task<IResult> TaskOperation();
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
using SPT_AKI_Installer.Aki.Core.Model;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace SPT_AKI_Installer.Aki.Core
|
namespace SPTInstaller.Models
|
||||||
{
|
{
|
||||||
public class InternalData
|
public class InternalData
|
||||||
{
|
{
|
76
SPTInstaller/Models/PreCheckBase.cs
Normal file
76
SPTInstaller/Models/PreCheckBase.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using SPTInstaller.Interfaces;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Models
|
||||||
|
{
|
||||||
|
public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||||
|
{
|
||||||
|
private string _id;
|
||||||
|
public string Id
|
||||||
|
{
|
||||||
|
get => _id;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _id, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _name;
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _required;
|
||||||
|
public bool IsRequired
|
||||||
|
{
|
||||||
|
get => _required;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _required, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _passed;
|
||||||
|
public bool Passed
|
||||||
|
{
|
||||||
|
get => _passed;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _passed, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isPending;
|
||||||
|
public bool IsPending
|
||||||
|
{
|
||||||
|
get => _isPending;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _isPending, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isRunning;
|
||||||
|
public bool IsRunning
|
||||||
|
{
|
||||||
|
get => _isRunning;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _isRunning, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
}
|
20
SPTInstaller/Models/Result.cs
Normal file
20
SPTInstaller/Models/Result.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using SPTInstaller.Interfaces;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Models
|
||||||
|
{
|
||||||
|
public class Result : IResult
|
||||||
|
{
|
||||||
|
public bool Succeeded { get; private set; }
|
||||||
|
|
||||||
|
public string Message { get; private set; }
|
||||||
|
|
||||||
|
protected Result(string message, bool succeeded)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
Succeeded = succeeded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result FromSuccess(string message = "") => new Result(message, true);
|
||||||
|
public static Result FromError(string message) => new Result(message, false);
|
||||||
|
}
|
||||||
|
}
|
64
SPTInstaller/Program.cs
Normal file
64
SPTInstaller/Program.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Splat;
|
||||||
|
using SPTInstaller.Controllers;
|
||||||
|
using SPTInstaller.Helpers;
|
||||||
|
using SPTInstaller.Installer_Tasks;
|
||||||
|
using SPTInstaller.Installer_Tasks.PreChecks;
|
||||||
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace SPTInstaller
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
|
||||||
|
|
||||||
|
// Register all the things
|
||||||
|
// Regestering as base classes so ReactiveUI works correctly. Doesn't seem to like the interfaces :(
|
||||||
|
ServiceHelper.Register<InternalData>();
|
||||||
|
ServiceHelper.Register<PreCheckBase, NetFramework472PreCheck>();
|
||||||
|
ServiceHelper.Register<PreCheckBase, NetCore6PreCheck>();
|
||||||
|
#if !TEST
|
||||||
|
ServiceHelper.Register<InstallerTaskBase, InitializationTask>();
|
||||||
|
ServiceHelper.Register<InstallerTaskBase, ReleaseCheckTask>();
|
||||||
|
ServiceHelper.Register<InstallerTaskBase, DownloadTask>();
|
||||||
|
ServiceHelper.Register<InstallerTaskBase, CopyClientTask>();
|
||||||
|
ServiceHelper.Register<InstallerTaskBase, SetupClientTask>();
|
||||||
|
#else
|
||||||
|
for(int i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
Locator.CurrentMutable.RegisterConstant<InstallerTaskBase>(TestTask.FromRandomName());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// need the interfaces for the controller and splat won't resolve them since we need to base classes in avalonia (what a mess), so doing it manually here
|
||||||
|
var tasks = Locator.Current.GetServices<InstallerTaskBase>().ToArray() as IProgressableTask[];
|
||||||
|
var preChecks = Locator.Current.GetServices<PreCheckBase>().ToArray() as IPreCheck[];
|
||||||
|
|
||||||
|
var installer = new InstallController(tasks, preChecks);
|
||||||
|
|
||||||
|
// manually register install controller
|
||||||
|
Locator.CurrentMutable.RegisterConstant(installer);
|
||||||
|
|
||||||
|
return AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.LogToTrace()
|
||||||
|
.UseReactiveUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
SPTInstaller/Roots.xml
Normal file
5
SPTInstaller/Roots.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<linker>
|
||||||
|
<!-- Can be removed if CompiledBinding and no reflection are used -->
|
||||||
|
<assembly fullname="SPTInstaller" preserve="All" />
|
||||||
|
<assembly fullname="Avalonia.Themes.Fluent" preserve="All" />
|
||||||
|
</linker>
|
42
SPTInstaller/SPTInstaller.csproj
Normal file
42
SPTInstaller/SPTInstaller.csproj
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<PackageIcon>icon.ico</PackageIcon>
|
||||||
|
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||||
|
<Configurations>Debug;Release;TEST</Configurations>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<AvaloniaResource Include="Assets\**" />
|
||||||
|
<None Remove=".gitignore" />
|
||||||
|
<None Remove="Assets\icon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<TrimmerRootDescriptor Include="Roots.xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Include="Avalonia" Version="0.10.19" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.19" />
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.19" />
|
||||||
|
<PackageReference Include="FubarCoder.RestSharp.Portable.HttpClient" Version="4.0.8" />
|
||||||
|
<PackageReference Include="MegaApiClient" Version="1.10.3" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
||||||
|
<PackageReference Include="XamlNameReferenceGenerator" Version="1.6.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Gitea">
|
||||||
|
<HintPath>Z:\dev_stuff\EftPatchHelper\EftPatchHelper\EftPatchHelper\Resources\Gitea.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
28
SPTInstaller/ViewLocator.cs
Normal file
28
SPTInstaller/ViewLocator.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using SPTInstaller.ViewModels;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SPTInstaller
|
||||||
|
{
|
||||||
|
public class ViewLocator : IDataTemplate
|
||||||
|
{
|
||||||
|
public IControl Build(object data)
|
||||||
|
{
|
||||||
|
var name = data.GetType().FullName!.Replace("ViewModel", "View");
|
||||||
|
var type = Type.GetType(name);
|
||||||
|
|
||||||
|
if (type != null)
|
||||||
|
{
|
||||||
|
return (Control)Activator.CreateInstance(type)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextBlock { Text = "Not Found: " + name };
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Match(object data)
|
||||||
|
{
|
||||||
|
return data is ViewModelBase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
SPTInstaller/ViewModels/InstallViewModel.cs
Normal file
38
SPTInstaller/ViewModels/InstallViewModel.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using SPTInstaller.Controllers;
|
||||||
|
using SPTInstaller.Helpers;
|
||||||
|
using SPTInstaller.Interfaces;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SPTInstaller.ViewModels
|
||||||
|
{
|
||||||
|
public class InstallViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private IProgressableTask _currentTask;
|
||||||
|
public IProgressableTask CurrentTask
|
||||||
|
{
|
||||||
|
get => _currentTask;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _currentTask, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<InstallerTaskBase> MyTasks { get; set; }
|
||||||
|
= new ObservableCollection<InstallerTaskBase>(ServiceHelper.GetAll<InstallerTaskBase>());
|
||||||
|
|
||||||
|
public InstallViewModel(IScreen host) : base(host)
|
||||||
|
{
|
||||||
|
var installer = ServiceHelper.Get<InstallController>();
|
||||||
|
|
||||||
|
installer.TaskChanged += (sender, task) => CurrentTask = task;
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var result = await installer.RunTasks();
|
||||||
|
|
||||||
|
NavigateTo(new MessageViewModel(HostScreen, result.Message));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
SPTInstaller/ViewModels/MainWindowViewModel.cs
Normal file
33
SPTInstaller/ViewModels/MainWindowViewModel.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace SPTInstaller.ViewModels
|
||||||
|
{
|
||||||
|
public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScreen
|
||||||
|
{
|
||||||
|
public RoutingState Router { get; } = new RoutingState();
|
||||||
|
public ViewModelActivator Activator { get; } = new ViewModelActivator();
|
||||||
|
|
||||||
|
public MainWindowViewModel()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
29
SPTInstaller/ViewModels/MessageViewModel.cs
Normal file
29
SPTInstaller/ViewModels/MessageViewModel.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using ReactiveUI;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace SPTInstaller.ViewModels
|
||||||
|
{
|
||||||
|
public class MessageViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private string _Message;
|
||||||
|
public string Message
|
||||||
|
{
|
||||||
|
get => _Message;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _Message, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand CloseCommand { get; set; } = ReactiveCommand.Create(() =>
|
||||||
|
{
|
||||||
|
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||||
|
{
|
||||||
|
desktopApp.MainWindow.Close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public MessageViewModel(IScreen Host, string message) : base(Host)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
SPTInstaller/ViewModels/PreChecksViewModel.cs
Normal file
53
SPTInstaller/ViewModels/PreChecksViewModel.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using ReactiveUI;
|
||||||
|
using SPTInstaller.Controllers;
|
||||||
|
using SPTInstaller.Helpers;
|
||||||
|
using SPTInstaller.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace SPTInstaller.ViewModels
|
||||||
|
{
|
||||||
|
public class PreChecksViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private string _InstallPath;
|
||||||
|
public string InstallPath
|
||||||
|
{
|
||||||
|
get => _InstallPath;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _InstallPath, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObservableCollection<PreCheckBase> PreChecks { get; set; }
|
||||||
|
= new ObservableCollection<PreCheckBase>(ServiceHelper.GetAll<PreCheckBase>());
|
||||||
|
|
||||||
|
ICommand StartInstallCommand { get; set; }
|
||||||
|
|
||||||
|
public PreChecksViewModel(IScreen host) : base(host)
|
||||||
|
{
|
||||||
|
var data = ServiceHelper.Get<InternalData>();
|
||||||
|
var installer = ServiceHelper.Get<InstallController>();
|
||||||
|
|
||||||
|
if(data == null || installer == null)
|
||||||
|
{
|
||||||
|
// TODO: abort to message view
|
||||||
|
}
|
||||||
|
|
||||||
|
data.TargetInstallPath = Environment.CurrentDirectory;
|
||||||
|
|
||||||
|
InstallPath = data.TargetInstallPath;
|
||||||
|
|
||||||
|
StartInstallCommand = ReactiveCommand.Create(() =>
|
||||||
|
{
|
||||||
|
NavigateTo(new InstallViewModel(HostScreen));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
// TODO: if a required precheck fails, abort to message view
|
||||||
|
var result = await installer.RunPreChecks();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
SPTInstaller/ViewModels/ViewModelBase.cs
Normal file
61
SPTInstaller/ViewModels/ViewModelBase.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using Avalonia.Threading;
|
||||||
|
using ReactiveUI;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SPTInstaller.ViewModels
|
||||||
|
{
|
||||||
|
public class ViewModelBase : ReactiveObject, IActivatableViewModel, IRoutableViewModel
|
||||||
|
{
|
||||||
|
public ViewModelActivator Activator { get; } = new ViewModelActivator();
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
SPTInstaller/Views/InstallView.axaml
Normal file
24
SPTInstaller/Views/InstallView.axaml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<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.InstallView">
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*, 2*">
|
||||||
|
<cc:ProgressableTaskList Tasks="{Binding MyTasks}"
|
||||||
|
Padding="20"
|
||||||
|
Background="{StaticResource AKI_Background_Dark}"
|
||||||
|
PendingColor="Gray"
|
||||||
|
RunningColor="DodgerBlue"
|
||||||
|
CompletedColor="{StaticResource AKI_Brush_Yellow}"
|
||||||
|
/>
|
||||||
|
<cc:TaskDetails Grid.Column="1"
|
||||||
|
Message="{Binding CurrentTask.StatusMessage}"
|
||||||
|
Details="{Binding CurrentTask.StatusDetails}"
|
||||||
|
Progress="{Binding CurrentTask.Progress}"
|
||||||
|
ShowProgress="{Binding CurrentTask.ShowProgress}"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
14
SPTInstaller/Views/InstallView.axaml.cs
Normal file
14
SPTInstaller/Views/InstallView.axaml.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using SPTInstaller.ViewModels;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Views
|
||||||
|
{
|
||||||
|
public partial class InstallView : ReactiveUserControl<InstallViewModel>
|
||||||
|
{
|
||||||
|
public InstallView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
SPTInstaller/Views/MainWindow.axaml
Normal file
37
SPTInstaller/Views/MainWindow.axaml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:vm="using:SPTInstaller.ViewModels"
|
||||||
|
xmlns:rxui="using:Avalonia.ReactiveUI"
|
||||||
|
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.MainWindow"
|
||||||
|
Icon="/Assets/icon.ico"
|
||||||
|
Title="SPT Installer"
|
||||||
|
Height="450" Width="750"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
ExtendClientAreaToDecorationsHint="True"
|
||||||
|
ExtendClientAreaChromeHints="NoChrome"
|
||||||
|
ExtendClientAreaTitleBarHeightHint="-1"
|
||||||
|
Background="{StaticResource AKI_Background_Light}"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Window.Styles>
|
||||||
|
<StyleInclude Source="/Assets/Styles.axaml"/>
|
||||||
|
</Window.Styles>
|
||||||
|
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<vm:MainWindowViewModel/>
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="AUTO,*">
|
||||||
|
<cc:TitleBar Title="SPT Installer"
|
||||||
|
XButtonCommand="{Binding CloseCommand}"
|
||||||
|
MinButtonCommand="{Binding MinimizeCommand}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<rxui:RoutedViewHost Router="{Binding Router}" Grid.Row="1"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
12
SPTInstaller/Views/MainWindow.axaml.cs
Normal file
12
SPTInstaller/Views/MainWindow.axaml.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Views
|
||||||
|
{
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
SPTInstaller/Views/MessageView.axaml
Normal file
26
SPTInstaller/Views/MessageView.axaml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<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.Views.MessageView">
|
||||||
|
<Grid ColumnDefinitions="*,AUTO,*" RowDefinitions="*,AUTO,*">
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="1" Spacing="20">
|
||||||
|
|
||||||
|
<Label Content="{Binding Message}" FontSize="18"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button Content="Close" Command="{Binding CloseCommand}"
|
||||||
|
FontSize="15" FontWeight="SemiBold"
|
||||||
|
Classes="yellow"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||||
|
Padding="20 10"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
14
SPTInstaller/Views/MessageView.axaml.cs
Normal file
14
SPTInstaller/Views/MessageView.axaml.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using SPTInstaller.ViewModels;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Views
|
||||||
|
{
|
||||||
|
public partial class MessageView : ReactiveUserControl<MessageViewModel>
|
||||||
|
{
|
||||||
|
public MessageView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
SPTInstaller/Views/PreChecksView.axaml
Normal file
47
SPTInstaller/Views/PreChecksView.axaml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<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.PreChecksView">
|
||||||
|
<Grid ColumnDefinitions="10,*,AUTO,*,10"
|
||||||
|
RowDefinitions="10,*,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"
|
||||||
|
/>
|
||||||
|
<Label Foreground="DodgerBlue"
|
||||||
|
Content="{Binding InstallPath}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
/>
|
||||||
|
<Label Content="Move the installer into the folder you want it to install into if this is wrong"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Grid.Column="2" Grid.Row="3" Content="Start Install" Padding="20 10"
|
||||||
|
Margin="10"
|
||||||
|
FontSize="15" FontWeight="SemiBold"
|
||||||
|
Classes="yellow"
|
||||||
|
Command="{Binding StartInstallCommand}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ItemsControl Items="{Binding PreChecks}" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="4" HorizontalAlignment="Center">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<cc:PreCheckItem PreCheckName="{Binding Name}"
|
||||||
|
IsRunning="{Binding IsRunning}"
|
||||||
|
IsPending="{Binding IsPending}"
|
||||||
|
IsRequired="{Binding IsRequired}"
|
||||||
|
Passed="{Binding Passed}"
|
||||||
|
/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
13
SPTInstaller/Views/PreChecksView.axaml.cs
Normal file
13
SPTInstaller/Views/PreChecksView.axaml.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia.ReactiveUI;
|
||||||
|
using SPTInstaller.ViewModels;
|
||||||
|
|
||||||
|
namespace SPTInstaller.Views
|
||||||
|
{
|
||||||
|
public partial class PreChecksView : ReactiveUserControl<PreChecksViewModel>
|
||||||
|
{
|
||||||
|
public PreChecksView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
SPTInstaller/app.manifest
Normal file
18
SPTInstaller/app.manifest
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<!-- This manifest is used on Windows only.
|
||||||
|
Don't remove it as it might cause problems with window transparency and embeded controls.
|
||||||
|
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- A list of the Windows versions that this application has been tested on
|
||||||
|
and is designed to work with. Uncomment the appropriate elements
|
||||||
|
and Windows will automatically select the most compatible environment. -->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
@ -1,41 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
|
||||||
<PackageIcon>icon.ico</PackageIcon>
|
|
||||||
<ApplicationIcon>Aki.Asset\icon.ico</ApplicationIcon>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="Aki.Asset\icon.ico" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="FubarCoder.RestSharp.Portable.Core" Version="4.0.8" />
|
|
||||||
<PackageReference Include="FubarCoder.RestSharp.Portable.HttpClient" Version="4.0.8" />
|
|
||||||
<PackageReference Include="MegaApiClient" Version="1.10.2" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
|
||||||
<PackageReference Include="SharpCompress" Version="0.32.1" />
|
|
||||||
<PackageReference Include="Spectre.Console" Version="0.44.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Gitea">
|
|
||||||
<HintPath>shared\Gitea.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="Aki.Asset\icon.ico">
|
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
<None Update="Aki.Asset\icon.jpeg">
|
|
||||||
<Pack>True</Pack>
|
|
||||||
<PackagePath>\</PackagePath>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
||||||
<PropertyGroup>
|
|
||||||
<_LastSelectedProfileId>Z:\dev_stuff\SPT-AKI-Installer\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.1.32407.343
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SPT_AKI_Installer", "SPT_AKI_Installer.csproj", "{7B07749A-3BE8-41B5-9B98-9F41C83FA15B}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{7B07749A-3BE8-41B5-9B98-9F41C83FA15B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{7B07749A-3BE8-41B5-9B98-9F41C83FA15B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{7B07749A-3BE8-41B5-9B98-9F41C83FA15B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{7B07749A-3BE8-41B5-9B98-9F41C83FA15B}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {6F8EE63B-3DC5-4168-A560-9B39F7785D84}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
Loading…
x
Reference in New Issue
Block a user