Compare commits
No commits in common. "master" and "feat-non-standard-windows" have entirely different histories.
master
...
feat-non-s
@ -1,4 +1,5 @@
|
||||
[*]
|
||||
|
||||
[*]
|
||||
charset = utf-8-bom
|
||||
end_of_line = crlf
|
||||
trim_trailing_whitespace = false
|
||||
@ -7,7 +8,7 @@ indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Microsoft .NET properties
|
||||
csharp_style_namespace_declarations = file_scoped:error
|
||||
csharp_style_namespace_declarations=file_scoped:error
|
||||
csharp_new_line_before_members_in_object_initializers = false
|
||||
csharp_preferred_modifier_order = public, private, protected, internal, file, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async, required:warning
|
||||
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||
|
8
.idea/.idea.SPTInstaller/.idea/avalonia.xml
generated
8
.idea/.idea.SPTInstaller/.idea/avalonia.xml
generated
@ -7,9 +7,6 @@
|
||||
<entry key="SPTInstaller/Assets/Styles.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/CacheInfo.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/DetailedPreCheckItem.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/Dialogs/ChangeLogDialog.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/Dialogs/ConfirmationDialog.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/Dialogs/MessageDialog.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/Dialogs/WhyCacheThoughDialog.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/MainInstallerButton.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/PreCheckDetails.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
@ -19,12 +16,7 @@
|
||||
<entry key="SPTInstaller/CustomControls/UpdateButton.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/CustomControls/UpdateInfoCard.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/DetailedPreChecksView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/InstallPathSelectionView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/InstallView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/InstallerUpdateView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/MainWindow.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/MessageView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/OverviewView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
<entry key="SPTInstaller/Views/PreChecksView.axaml" value="SPTInstaller/SPTInstaller.csproj" />
|
||||
</map>
|
||||
</option>
|
||||
|
24
README.md
24
README.md
@ -1,20 +1,20 @@
|
||||
# SPT Installer made for EFT.
|
||||
# SPT-AKI Installer made for EFT.
|
||||
|
||||
<img src="https://i.imgur.com/jtlwLsr.png" alt="spt installer 2.59" width="700"/>
|
||||
![Installer Image](https://media.discordapp.net/attachments/875707258074447904/1107352250705268807/image.png?width=1148&height=671)
|
||||
|
||||
### Pre install checks:
|
||||
- Checks if .net 4.7.2 (or higher) is installed
|
||||
- Checks if .net 8 runtime is installed
|
||||
- Checks if EFT is installed
|
||||
- Checks if there is enough space before install
|
||||
- Checks installer is not in a problematic path
|
||||
- Checks install folder does not have game files already in it
|
||||
- Checks if gameversion matches SPT version, if so skip patcher process
|
||||
- Checks if .net 6 desktop runtime is installed
|
||||
- Checks if EFT is installed,
|
||||
- Checks if there is enough space before install,
|
||||
- Checks installer is not in OG game directory,
|
||||
- Checks install folder does not have game files already in it,
|
||||
- Checks if gameversion matches aki version, if so skip patcher process,
|
||||
- Checks both zips are there, other than when the above match, patcher isnt checked for
|
||||
- downloads both Zips from the Repo's if needed
|
||||
|
||||
### Installer Processes:
|
||||
- Copies files from registry logged GamePath to new location
|
||||
- Extracts, runs and deletes patcher with no user input
|
||||
- Extracts SPT
|
||||
- Deletes both Patcher and SPT zips at the end
|
||||
- Copies files from registry logged GamePath to new location,
|
||||
- Extracts, runs and deletes patcher with no user input,
|
||||
- Extracts Aki,
|
||||
- Deletes both Patcher and AKI zips at the end.
|
@ -5,7 +5,7 @@
|
||||
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
RequestedThemeVariant="Light">
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator />
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
@ -13,43 +13,38 @@
|
||||
<dialogHostAvalonia:DialogHostStyles />
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<Application.Resources>
|
||||
|
||||
<!-- Colors -->
|
||||
<Color x:Key="SPT_DarkGray">#121212</Color>
|
||||
<Color x:Key="SPT_Yellow">#FFC107</Color>
|
||||
<Color x:Key="SPT_White">#FFFFFF</Color>
|
||||
<Color x:Key="SPT_Gray">#282828</Color>
|
||||
<Color x:Key="SPT_DarkGrayBlue">#323947</Color>
|
||||
<Color x:Key="SPT_LightGrayBlue">#444259</Color>
|
||||
<!-- 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>
|
||||
<Color x:Key="AKI_LightGrayBlue">#444259</Color>
|
||||
|
||||
<!-- Brushes -->
|
||||
<SolidColorBrush x:Key="SPT_Foreground_Light" Color="{StaticResource SPT_White}" />
|
||||
<SolidColorBrush x:Key="SPT_Background_Light" Color="{StaticResource SPT_Gray}" />
|
||||
<SolidColorBrush x:Key="SPT_Background_Dark" Color="{StaticResource SPT_DarkGray}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_Yellow" Color="{StaticResource SPT_Yellow}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_DarkGrayBlue" Color="{StaticResource SPT_DarkGrayBlue}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_LightGrayBlue" Color="{StaticResource SPT_LightGrayBlue}" />
|
||||
<SolidColorBrush x:Key="SPT_Brush_Lighter" Color="Gainsboro" />
|
||||
<!-- 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_LightGrayBlue" Color="{StaticResource AKI_LightGrayBlue}"/>
|
||||
<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" />
|
||||
<PathGeometry x:Key="Cache"
|
||||
Figures="M 5.4515409 18.707476 C 2.8719183 18.411612 0.6356275 17.508046 0.12886774 16.556868 0.011421 16.336424 0 16.17685 0 14.75659 v -1.558391 l 0.20348761 0.163959 c 0.89061819 0.717606 2.90294209 1.394851 4.91765599 1.655024 1.0475327 0.135279 3.5115239 0.155131 4.4933927 0.03621 2.1668367 -0.262447 3.9985147 -0.826312 5.0629357 -1.558573 l 0.322527 -0.221877 v 1.524757 c 0 1.755973 -0.0041 1.772626 -0.56529 2.263709 -0.796666 0.697209 -2.144167 1.203544 -4.0933 1.53809 -0.5767809 0.099 -1.1020996 0.129287 -2.5440527 0.146688 -0.9994482 0.01206 -2.0550657 -0.0054 -2.3458154 -0.0387 z M 7.8601306 17.28745 c 0.0527 -0.04941 0.095816 -0.193581 0.095816 -0.320366 0 -0.405037 -0.46603 -0.61585 -0.7533032 -0.340767 -0.1321068 0.126504 -0.1782781 0.469731 -0.081588 0.606525 0.1545094 0.218594 0.5342719 0.246654 0.7390756 0.05461 z M 5.5458634 17.092351 c 0.075327 -0.0917 0.1369546 -0.21599 0.1369546 -0.276208 0 -0.203883 -0.2583299 -0.442927 -0.478668 -0.442927 -0.2881074 0 -0.4464405 0.159294 -0.4464405 0.449148 0 0.424533 0.5160004 0.601292 0.7881539 0.269987 z m 4.7625076 0.04017 c 0.07342 -0.0703 0.132157 -0.210917 0.132157 -0.316377 0 -0.105459 -0.05874 -0.24607 -0.132157 -0.316375 -0.07342 -0.0703 -0.220267 -0.126552 -0.3303979 -0.126552 -0.1101318 0 -0.2569762 0.05625 -0.3303975 0.126552 -0.073421 0.0703 -0.1321569 0.210916 -0.1321569 0.316375 0 0.10546 0.058738 0.246072 0.1321569 0.316377 0.073421 0.07031 0.2202657 0.126551 0.3303975 0.126551 0.1101309 0 0.2569759 -0.05624 0.3303979 -0.126551 z M 6.4427306 14.219282 C 3.438293 14.06843 0.69941831 13.09766 0.13645011 11.98407 0.00570426 11.725447 -0.00143606 11.622911 0.01527712 10.244516 L 0.03303424 8.7797875 0.36343157 9.0065262 C 2.6575042 10.580863 7.8536269 11.112097 11.747571 10.170399 12.962111 9.8766787 13.852599 9.520745 14.620039 9.0222575 l 0.379955 -0.2467994 v 1.4852079 c 0 0.822685 -0.02906 1.558392 -0.06514 1.649265 -0.108197 0.272498 -0.66301 0.788548 -1.121691 1.043315 -1.168844 0.64922 -3.065811 1.13473 -4.8263882 1.235267 -1.2569769 0.07178 -1.6359325 0.07636 -2.5440532 0.03076 z m 1.4159088 -1.492928 c 0.075324 -0.0917 0.1369543 -0.215989 0.1369543 -0.276207 0 -0.203883 -0.2583318 -0.442926 -0.4786675 -0.442926 -0.2881093 0 -0.4464425 0.159291 -0.4464425 0.449146 0 0.424534 0.5160019 0.601293 0.7881557 0.269987 z m -2.29146 -0.159368 c 0.1618895 -0.183199 0.1439776 -0.411835 -0.046556 -0.594283 -0.3090962 -0.29598 -0.7629143 -0.107341 -0.7629143 0.317126 0 0.426014 0.5204584 0.604217 0.8094703 0.277157 z m 4.6992386 0.06839 c 0.12628 -0.09512 0.17411 -0.18946 0.17411 -0.343424 0 -0.232297 -0.242217 -0.474564 -0.4744705 -0.474564 -0.1820319 0 -0.4506387 0.282868 -0.4506387 0.474564 0 0.191699 0.2686068 0.474567 0.4506387 0.474567 0.069437 0 0.2046015 -0.05901 0.3003605 -0.131143 z M 5.9140951 9.6877494 C 3.2886712 9.3885406 1.5607385 8.8497481 0.58264334 8.0253262 0.03362115 7.5625637 0 7.4352112 0 5.8183118 V 4.4150124 L 0.30935351 4.6411272 C 1.3355178 5.3911787 3.3497821 5.9987632 5.5506611 6.2221182 6.7548513 6.3443244 9.00242 6.3139162 10.077093 6.1608788 12.027869 5.8830787 13.636 5.3630162 14.620197 4.6916622 14.792913 4.5738469 14.949026 4.4774523 14.967113 4.4774523 14.985194 4.4774523 15 5.1050107 15 5.8720299 15 7.4791794 14.982469 7.5435731 14.411053 8.0355275 13.533871 8.7907237 11.69734 9.3913644 9.5158238 9.6365256 8.7752594 9.71975 6.4811675 9.7523775 5.9140951 9.6877506 Z M 7.7751038 8.4969125 C 8.1763294 8.227805 7.8985219 7.5555425 7.4323869 7.6675706 7.1999381 7.7234394 7.0703688 7.8802994 7.071495 8.104485 7.072745 8.3355694 7.11687 8.4143437 7.3017606 8.5147362 7.4816994 8.6124437 7.610055 8.6076106 7.7751038 8.4969175 Z M 5.5458634 8.2338012 C 5.6211901 8.1421037 5.682818 8.0178119 5.682818 7.9575956 c 0 -0.1899694 -0.2585097 -0.4429287 -0.4526467 -0.4429287 -0.2913367 0 -0.4724618 0.1632037 -0.4724618 0.425715 0 0.2931318 0.1578686 0.4601406 0.4349596 0.4601406 0.154904 0 0.2550881 -0.047287 0.3531943 -0.1667213 z m 4.7625076 0.040175 c 0.07342 -0.070305 0.132157 -0.2109162 0.132157 -0.316375 0 -0.1054612 -0.05874 -0.2460718 -0.132157 -0.3163775 -0.286005 -0.2738662 -0.7929522 -0.071601 -0.7929522 0.3163775 0 0.1054588 0.058737 0.2460694 0.1321568 0.316375 0.073421 0.070304 0.2202657 0.1265519 0.3303975 0.1265519 0.1101309 0 0.2569759 -0.056244 0.3303979 -0.1265519 z M 6.178414 5.3866444 C 3.8630228 5.2033161 2.0080132 4.6795372 0.82192238 3.8741859 0.26331487 3.4948944 0.03303924 3.1533125 0.03303924 2.7039893 c 0 -0.2939821 0.03374352 -0.3937029 0.21156697 -0.6252455 C 1.4428273 0.518552 5.8324898 -0.36375235 9.8724944 0.14357696 12.82408 0.51422624 14.999999 1.6015126 14.999999 2.7057413 c 0 1.1212199 -2.227472 2.2145851 -5.2863421 2.5948303 C 9.1252344 5.3737159 6.74658 5.4316311 6.178414 5.3866444 Z"
|
||||
FillRule="NonZero" />
|
||||
<!-- 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"
|
||||
/>
|
||||
<PathGeometry x:Key="Cache" Figures="M 5.4515409 18.707476 C 2.8719183 18.411612 0.6356275 17.508046 0.12886774 16.556868 0.011421 16.336424 0 16.17685 0 14.75659 v -1.558391 l 0.20348761 0.163959 c 0.89061819 0.717606 2.90294209 1.394851 4.91765599 1.655024 1.0475327 0.135279 3.5115239 0.155131 4.4933927 0.03621 2.1668367 -0.262447 3.9985147 -0.826312 5.0629357 -1.558573 l 0.322527 -0.221877 v 1.524757 c 0 1.755973 -0.0041 1.772626 -0.56529 2.263709 -0.796666 0.697209 -2.144167 1.203544 -4.0933 1.53809 -0.5767809 0.099 -1.1020996 0.129287 -2.5440527 0.146688 -0.9994482 0.01206 -2.0550657 -0.0054 -2.3458154 -0.0387 z M 7.8601306 17.28745 c 0.0527 -0.04941 0.095816 -0.193581 0.095816 -0.320366 0 -0.405037 -0.46603 -0.61585 -0.7533032 -0.340767 -0.1321068 0.126504 -0.1782781 0.469731 -0.081588 0.606525 0.1545094 0.218594 0.5342719 0.246654 0.7390756 0.05461 z M 5.5458634 17.092351 c 0.075327 -0.0917 0.1369546 -0.21599 0.1369546 -0.276208 0 -0.203883 -0.2583299 -0.442927 -0.478668 -0.442927 -0.2881074 0 -0.4464405 0.159294 -0.4464405 0.449148 0 0.424533 0.5160004 0.601292 0.7881539 0.269987 z m 4.7625076 0.04017 c 0.07342 -0.0703 0.132157 -0.210917 0.132157 -0.316377 0 -0.105459 -0.05874 -0.24607 -0.132157 -0.316375 -0.07342 -0.0703 -0.220267 -0.126552 -0.3303979 -0.126552 -0.1101318 0 -0.2569762 0.05625 -0.3303975 0.126552 -0.073421 0.0703 -0.1321569 0.210916 -0.1321569 0.316375 0 0.10546 0.058738 0.246072 0.1321569 0.316377 0.073421 0.07031 0.2202657 0.126551 0.3303975 0.126551 0.1101309 0 0.2569759 -0.05624 0.3303979 -0.126551 z M 6.4427306 14.219282 C 3.438293 14.06843 0.69941831 13.09766 0.13645011 11.98407 0.00570426 11.725447 -0.00143606 11.622911 0.01527712 10.244516 L 0.03303424 8.7797875 0.36343157 9.0065262 C 2.6575042 10.580863 7.8536269 11.112097 11.747571 10.170399 12.962111 9.8766787 13.852599 9.520745 14.620039 9.0222575 l 0.379955 -0.2467994 v 1.4852079 c 0 0.822685 -0.02906 1.558392 -0.06514 1.649265 -0.108197 0.272498 -0.66301 0.788548 -1.121691 1.043315 -1.168844 0.64922 -3.065811 1.13473 -4.8263882 1.235267 -1.2569769 0.07178 -1.6359325 0.07636 -2.5440532 0.03076 z m 1.4159088 -1.492928 c 0.075324 -0.0917 0.1369543 -0.215989 0.1369543 -0.276207 0 -0.203883 -0.2583318 -0.442926 -0.4786675 -0.442926 -0.2881093 0 -0.4464425 0.159291 -0.4464425 0.449146 0 0.424534 0.5160019 0.601293 0.7881557 0.269987 z m -2.29146 -0.159368 c 0.1618895 -0.183199 0.1439776 -0.411835 -0.046556 -0.594283 -0.3090962 -0.29598 -0.7629143 -0.107341 -0.7629143 0.317126 0 0.426014 0.5204584 0.604217 0.8094703 0.277157 z m 4.6992386 0.06839 c 0.12628 -0.09512 0.17411 -0.18946 0.17411 -0.343424 0 -0.232297 -0.242217 -0.474564 -0.4744705 -0.474564 -0.1820319 0 -0.4506387 0.282868 -0.4506387 0.474564 0 0.191699 0.2686068 0.474567 0.4506387 0.474567 0.069437 0 0.2046015 -0.05901 0.3003605 -0.131143 z M 5.9140951 9.6877494 C 3.2886712 9.3885406 1.5607385 8.8497481 0.58264334 8.0253262 0.03362115 7.5625637 0 7.4352112 0 5.8183118 V 4.4150124 L 0.30935351 4.6411272 C 1.3355178 5.3911787 3.3497821 5.9987632 5.5506611 6.2221182 6.7548513 6.3443244 9.00242 6.3139162 10.077093 6.1608788 12.027869 5.8830787 13.636 5.3630162 14.620197 4.6916622 14.792913 4.5738469 14.949026 4.4774523 14.967113 4.4774523 14.985194 4.4774523 15 5.1050107 15 5.8720299 15 7.4791794 14.982469 7.5435731 14.411053 8.0355275 13.533871 8.7907237 11.69734 9.3913644 9.5158238 9.6365256 8.7752594 9.71975 6.4811675 9.7523775 5.9140951 9.6877506 Z M 7.7751038 8.4969125 C 8.1763294 8.227805 7.8985219 7.5555425 7.4323869 7.6675706 7.1999381 7.7234394 7.0703688 7.8802994 7.071495 8.104485 7.072745 8.3355694 7.11687 8.4143437 7.3017606 8.5147362 7.4816994 8.6124437 7.610055 8.6076106 7.7751038 8.4969175 Z M 5.5458634 8.2338012 C 5.6211901 8.1421037 5.682818 8.0178119 5.682818 7.9575956 c 0 -0.1899694 -0.2585097 -0.4429287 -0.4526467 -0.4429287 -0.2913367 0 -0.4724618 0.1632037 -0.4724618 0.425715 0 0.2931318 0.1578686 0.4601406 0.4349596 0.4601406 0.154904 0 0.2550881 -0.047287 0.3531943 -0.1667213 z m 4.7625076 0.040175 c 0.07342 -0.070305 0.132157 -0.2109162 0.132157 -0.316375 0 -0.1054612 -0.05874 -0.2460718 -0.132157 -0.3163775 -0.286005 -0.2738662 -0.7929522 -0.071601 -0.7929522 0.3163775 0 0.1054588 0.058737 0.2460694 0.1321568 0.316375 0.073421 0.070304 0.2202657 0.1265519 0.3303975 0.1265519 0.1101309 0 0.2569759 -0.056244 0.3303979 -0.1265519 z M 6.178414 5.3866444 C 3.8630228 5.2033161 2.0080132 4.6795372 0.82192238 3.8741859 0.26331487 3.4948944 0.03303924 3.1533125 0.03303924 2.7039893 c 0 -0.2939821 0.03374352 -0.3937029 0.21156697 -0.6252455 C 1.4428273 0.518552 5.8324898 -0.36375235 9.8724944 0.14357696 12.82408 0.51422624 14.999999 1.6015126 14.999999 2.7057413 c 0 1.1212199 -2.227472 2.2145851 -5.2863421 2.5948303 C 9.1252344 5.3737159 6.74658 5.4316311 6.178414 5.3866444 Z" FillRule="NonZero"
|
||||
/>
|
||||
<PathGeometry x:Key="Bug"
|
||||
Figures="m 12.25 0 a 0.75 0.75 0 0 1 0.743 0.648 L 13 0.75 v 0.752 c 0 0.633 -0.196 1.22 -0.53 1.704 a 3.75 3.75 0 0 1 2.521 3.29 h 0.256 a 2.25 2.25 0 0 0 2.24 -2.259 L 17.481 2.752 a 0.750006 0.750006 0 0 1 1.5 -0.006 l 0.007 1.485 a 3.75 3.75 0 0 1 -3.536 3.76 L 15.238 7.997 L 15 7.996 v 1.502 h 4.253 a 0.75 0.75 0 0 1 0.743 0.649 l 0.007 0.102 a 0.75 0.75 0 0 1 -0.648 0.743 l -0.102 0.007 H 15 v 1.999 h 0.238 l 0.214 0.007 a 3.75 3.75 0 0 1 3.531 3.56 l 0.005 0.2 l -0.007 1.485 a 0.75 0.75 0 0 1 -1.493 0.095 l -0.007 -0.102 l 0.007 -1.485 a 2.25 2.25 0 0 0 -2.087 -2.253 l -0.154 -0.006 h -0.476 a 5.002 5.002 0 0 1 -9.542 0 H 4.74 A 2.25 2.25 0 0 0 2.5 16.758 l 0.005 1.485 a 0.750008 0.750008 0 1 1 -1.5 0.007 L 1 16.764 a 3.75 3.75 0 0 1 3.535 -3.76 L 4.75 12.999 L 5 12.998 v -2 H 0.75 A 0.75 0.75 0 0 1 0.007 10.35 L 0 10.249 A 0.75 0.75 0 0 1 0.648 9.506 L 0.75 9.499 L 5 9.498 V 7.996 H 4.75 L 4.535 7.991 A 3.75 3.75 0 0 1 1.005 4.431 L 1 4.23 L 1.006 2.745 A 0.75 0.75 0 0 1 2.5 2.649 L 2.506 2.751 L 2.5 4.237 A 2.25 2.25 0 0 0 4.587 6.491 L 4.741 6.497 H 5.009 A 3.753 3.753 0 0 1 7.53 3.205 A 2.968 2.968 0 0 1 7.006 1.711 L 7 1.502 V 0.75 A 0.75 0.75 0 0 1 8.493 0.648 L 8.5 0.75 v 0.752 a 1.5 1.5 0 0 0 2.993 0.145 L 11.5 1.502 V 0.75 A 0.75 0.75 0 0 1 12.25 0 Z"
|
||||
FillRule="NonZero" />
|
||||
<PathGeometry x:Key="OpenFolder" Figures="M 2.2731724 14.474999 C 2.5381753 14.186249 3.2824783 12.195001 3.9271792 10.05 5.6676413 4.2592679 4.7621113 4.8000009 12.719033 4.8000009 c 5.6684 0 6.78597 0.072438 7.12511 0.4618343 0.332844 0.3821726 0.17704 1.1971998 -0.903259 4.7250006 -0.763041 2.4917722 -1.52781 4.4189802 -1.840552 4.6381652 C 16.708149 14.899859 14.592619 15 9.1783054 15 2.1694393 15 1.8160107 14.973129 2.2731724 14.474999 Z M 0.36305228 14.025959 C 0.11166709 13.786409 0 11.721164 0 7.3114288 0 1.9218189 0.0760474 0.8703905 0.49472143 0.47142828 0.8806724 0.10364926 1.7051307 0 4.2446088 0 7.4749739 0 7.5058294 0.00685701 8.2944922 0.89999983 L 9.0892098 1.8 h 3.6407872 c 3.221023 0 3.71338 0.069177 4.270431 0.5999996 0.346306 0.3300009 0.629646 0.802501 0.629646 1.0500009 0 0.3838238 -0.858607 0.4500002 -5.83853 0.4500002 -5.6986082 0 -5.856156 0.016794 -6.5739181 0.7007613 C 4.8131633 4.9861817 4.2426547 6.0999322 3.9498292 7.0757619 2.3566037 12.385128 1.8127023 13.81777 1.2887903 14.084957 c -0.37832867 0.192941 -0.68163535 0.173611 -0.92573802 -0.059 z"
|
||||
FillRule="NonZero" />
|
||||
|
||||
</Application.Resources>
|
||||
FillRule="NonZero"
|
||||
/>
|
||||
</Application.Resources>
|
||||
</Application>
|
@ -1,5 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
@ -8,36 +7,12 @@ using Serilog;
|
||||
using SPTInstaller.ViewModels;
|
||||
using SPTInstaller.Views;
|
||||
using System.Reactive;
|
||||
using System.Text;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public static string LogPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer", "spt-installer.log");
|
||||
public static string LogDebugPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"spt-installer", "spt-isntaller-debug.log");
|
||||
|
||||
public static void ReLaunch(bool debug, string installPath = "")
|
||||
{
|
||||
var installerPath = Path.Join(Environment.CurrentDirectory, "SPTInstaller.exe");
|
||||
|
||||
var args = new StringBuilder()
|
||||
.Append(debug ? "debug " : "")
|
||||
.Append(!string.IsNullOrEmpty(installPath) ? $"installPath=\"{installPath}\"" : "")
|
||||
.ToString();
|
||||
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = installerPath,
|
||||
Arguments = args
|
||||
});
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
private readonly string _logPath = Path.Join(Environment.CurrentDirectory, "spt-aki-installer_.log");
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
@ -45,52 +20,41 @@ public partial class App : Application
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.WriteTo
|
||||
.File(path: LogPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information)
|
||||
.File(path: _logPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information,
|
||||
rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
RxApp.DefaultExceptionHandler = Observer.Create<Exception>((exception) =>
|
||||
{
|
||||
Log.Error(exception, "An application exception occurred");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var data = ServiceHelper.Get<InternalData>() ?? throw new Exception("failed to get internal data");
|
||||
|
||||
data.DebugMode = false;
|
||||
var providedPath = "";
|
||||
|
||||
if (desktop.Args != null)
|
||||
var debug = desktop.Args != null && desktop.Args.Any(x => x.ToLower() == "debug");
|
||||
if (debug)
|
||||
{
|
||||
data.DebugMode = desktop.Args.Any(x => x.ToLower() == "debug");
|
||||
var installPath = desktop.Args.FirstOrDefault(x => x.StartsWith("installPath=", StringComparison.CurrentCultureIgnoreCase));
|
||||
|
||||
providedPath = installPath != null && installPath.Contains('=') ? installPath?.Split('=')[1] ?? "" : "";
|
||||
}
|
||||
|
||||
if (data.DebugMode)
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo
|
||||
.File(path: LogDebugPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug)
|
||||
.File(path: _logPath,
|
||||
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug,
|
||||
rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
Trace.Listeners.Add(new SerilogTraceListener.SerilogTraceListener());
|
||||
System.Diagnostics.Trace.Listeners.Add(new SerilogTraceListener.SerilogTraceListener());
|
||||
|
||||
Log.Debug("TraceListener is registered");
|
||||
}
|
||||
|
||||
desktop.MainWindow = new MainWindow
|
||||
{
|
||||
DataContext = new MainWindowViewModel(providedPath),
|
||||
DataContext = new MainWindowViewModel(debug),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,297 +1,269 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:cc="using:SPTInstaller.CustomControls">
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Spacing="5" Background="{StaticResource SPT_Background_Dark}">
|
||||
<Button Classes="icon" x:Name="testBtn">
|
||||
xmlns:cc="using:SPTInstaller.CustomControls"
|
||||
>
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Spacing="5" Background="{StaticResource AKI_Background_Dark}">
|
||||
<Button Classes="icon" x:Name="testBtn">
|
||||
<Path Data="{StaticResource Bug}"
|
||||
Fill="{Binding ElementName=testBtn, Path=Foreground}" />
|
||||
Fill="{Binding ElementName=testBtn, Path=Foreground}"
|
||||
/>
|
||||
</Button>
|
||||
<TextBox Text="Some cool text here" Margin="5" />
|
||||
<TextBox Watermark="This is a watermark" Margin="5" />
|
||||
<CheckBox Content="sldkflskdf" />
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
<TextBox Text="Some cool text here" Margin="5"/>
|
||||
<TextBox Watermark="This is a watermark" Margin="5"/>
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<!-- Add Styles Here -->
|
||||
<!-- Add Styles Here -->
|
||||
|
||||
<!-- TitleBar Styles -->
|
||||
<Style Selector="cc|TitleBar">
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Foreground_Light}" />
|
||||
<Setter Property="ButtonForeground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
<!-- 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 SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 2" />
|
||||
</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 SPT_Background_Light}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
</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 SPT_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 SPT_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/ 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: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: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 /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 SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</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>
|
||||
|
||||
<!-- TextBlock Styles -->
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Foreground_Light}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Foreground_Light}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Label Styles -->
|
||||
<!-- SourceRef: https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Label.xaml -->
|
||||
<Style Selector="Label">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Foreground_Light}" />
|
||||
</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 SPT_Brush_Yellow}" />
|
||||
</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.dark">
|
||||
<Setter Property="Foreground" Value="DimGray"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Label.versionMismatch">
|
||||
<Setter Property="Foreground" Value="OrangeRed" />
|
||||
</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 SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
</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 SPT_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>
|
||||
<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 SPT_Background_Dark}" />
|
||||
</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 SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_White}" />
|
||||
</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_White}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource SPT_LightGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_LightGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_White}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="Button:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource AKI_LightGrayBlue}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_LightGrayBlue}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_White}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
</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 SPT_Brush_DarkGrayBlue}" />
|
||||
</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 SPT_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</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">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Gold" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="Button.yellow:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Gold"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.yellow:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
</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 SPT_Brush_DarkGrayBlue}" />
|
||||
</Style>
|
||||
<Style Selector="Button.yellow:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button outlined Style -->
|
||||
<Style Selector="Button.outlined">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlined:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="2" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlined:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<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>
|
||||
|
||||
<!-- Button Link Style -->
|
||||
<Style Selector="Button.link">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1"/>
|
||||
<Setter Property="ContentTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding $parent[Button].Content}" TextDecorations="Underline" />
|
||||
<TextBlock Text="{Binding $parent[Button].Content}" TextDecorations="Underline"/>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pointerover TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pressed TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.link:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1" />
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0 0 0 1"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button outlinedTLCorner Style -->
|
||||
<Style Selector="Button.outlinedTLCorner">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Lighter}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="2 2 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Button.outlinedTLCorner">
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Lighter}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="2 2 0 0"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlinedTLCorner:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="BorderThickness" Value="2 2 0 0" />
|
||||
</Style>
|
||||
<Style Selector="Button.outlinedTLCorner:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
<Setter Property="BorderThickness" Value="2 2 0 0"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.outlinedTLCorner:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="Button.outlinedTLCorner:pressed /template/ ContentPresenter">
|
||||
<Setter Property="TextBlock.Foreground" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button icon Style -->
|
||||
<Style Selector="Button.icon">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
<Style Selector="Button.icon:pointerover">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Brush_Yellow}" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Brush_Yellow}"/>
|
||||
</Style>
|
||||
<Style Selector="Button.icon:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style Selector="Button.icon:pressed">
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_DarkGrayBlue}"></Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Checkbox Styles -->
|
||||
<Style Selector="CheckBox">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Style.Resources>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeUnchecked" Color="DimGray"/>
|
||||
</Style.Resources>
|
||||
</Style>
|
||||
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#ContentPresenter">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:checked /template/ ContentPresenter#ContentPresenter">
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="CheckBox:pointerover /template/ Border#NormalRectangle">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Yellow}"/>
|
||||
</Style>
|
||||
<Style Selector="CheckBox:checked /template/ Border#NormalRectangle">
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Yellow}"/>
|
||||
<Setter Property="Background" Value="{StaticResource SPT_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
<Style Selector="CheckBox:checked /template/ Path#CheckGlyph">
|
||||
<Setter Property="Fill" Value="{StaticResource SPT_Yellow}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_DarkGrayBlue}"></Setter>
|
||||
</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 |
Binary file not shown.
Before Width: | Height: | Size: 156 KiB |
@ -5,8 +5,7 @@ namespace SPTInstaller.Behaviors;
|
||||
|
||||
public class SpanBehavior : AvaloniaObject
|
||||
{
|
||||
public static readonly AttachedProperty<bool> SpanProperty =
|
||||
AvaloniaProperty.RegisterAttached<SpanBehavior, Interactive, bool>("Span");
|
||||
public static readonly AttachedProperty<bool> SpanProperty = AvaloniaProperty.RegisterAttached<SpanBehavior, Interactive, bool>("Span");
|
||||
|
||||
public static void SetSpan(AvaloniaObject element, bool value)
|
||||
{
|
||||
|
@ -58,8 +58,7 @@ public class InstallController
|
||||
{
|
||||
var result = await check.RunCheck();
|
||||
|
||||
Log.Information(
|
||||
$"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}\nDetail: {check.PreCheckDetails.ReplaceLineEndings(" ")}");
|
||||
Log.Information($"PreCheck: {check.Name} ({(check.IsRequired ? "Required" : "Optional")}) -> {(result.Succeeded ? "Passed" : "Failed")}\nDetail: {check.PreCheckDetails.ReplaceLineEndings(" ")}");
|
||||
|
||||
if (check.IsRequired)
|
||||
{
|
||||
|
@ -3,7 +3,6 @@ using SPTInstaller.CustomControls;
|
||||
using System.Globalization;
|
||||
|
||||
namespace SPTInstaller.Converters;
|
||||
|
||||
public class StatusSpinnerIsProcessingConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
|
@ -3,7 +3,6 @@ using SPTInstaller.CustomControls;
|
||||
using System.Globalization;
|
||||
|
||||
namespace SPTInstaller.Converters;
|
||||
|
||||
public class StatusSpinnerIsStateConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
|
@ -7,24 +7,25 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.CacheInfo">
|
||||
<UserControl.Resources>
|
||||
<convt:StatusSpinnerIsProcessingConverter x:Key="IsInProcessingStateConverter" />
|
||||
<convt:StatusSpinnerIsProcessingConverter x:Key="IsInProcessingStateConverter"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,AUTO,10,AUTO,*">
|
||||
<cc:StatusSpinner Grid.Column="1"
|
||||
State="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
<cc:StatusSpinner Grid.Column="1" State="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
IsVisible="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource IsInProcessingStateConverter}}" />
|
||||
Converter={StaticResource IsInProcessingStateConverter}}"
|
||||
/>
|
||||
|
||||
<Path Grid.Column="1" Data="{StaticResource Cache}" Fill="DodgerBlue" Margin="0 6 0 0"
|
||||
IsVisible="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource IsInProcessingStateConverter},
|
||||
ConverterParameter=invert}" />
|
||||
ConverterParameter=invert}"
|
||||
/>
|
||||
|
||||
<Label Grid.Column="3" Content="{Binding InfoText, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Margin="0 2 0 0" />
|
||||
<Button Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Content="What's this?" Classes="link"
|
||||
HorizontalAlignment="Center"
|
||||
Command="{Binding ShowCacheDialogCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
Margin="0 2 0 0"
|
||||
/>
|
||||
<Button Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" Content="What's this?" Classes="link" HorizontalAlignment="Center"
|
||||
Command="{Binding ShowCacheDialogCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -5,7 +5,6 @@ using SPTInstaller.CustomControls.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPTInstaller.CustomControls;
|
||||
|
||||
public partial class CacheInfo : UserControl
|
||||
{
|
||||
public CacheInfo()
|
||||
|
@ -8,18 +8,21 @@
|
||||
MinWidth="300" MinHeight="100"
|
||||
MaxWidth="600" MaxHeight="300">
|
||||
<Grid RowDefinitions="10,AUTO,*,AUTO,10" ColumnDefinitions="10,*,AUTO,10,AUTO,10"
|
||||
Background="{StaticResource SPT_Background_Light}">
|
||||
Background="{StaticResource AKI_Background_Light}">
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="4"
|
||||
Text="{Binding Message, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
TextWrapping="Wrap" />
|
||||
TextWrapping="Wrap"
|
||||
/>
|
||||
<Button Content="No" Grid.Row="3" Grid.Column="2"
|
||||
Width="50" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dialogHost:DialogHost}, Path=CloseDialogCommand}"
|
||||
CommandParameter="False"
|
||||
Classes="yellow" />
|
||||
Classes="yellow"
|
||||
/>
|
||||
<Button Content="Yes" Grid.Row="3" Grid.Column="4"
|
||||
Width="50" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dialogHost:DialogHost}, Path=CloseDialogCommand}"
|
||||
CommandParameter="True" />
|
||||
CommandParameter="True"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -2,7 +2,6 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace SPTInstaller.CustomControls.Dialogs;
|
||||
|
||||
public partial class ConfirmationDialog : UserControl
|
||||
{
|
||||
public ConfirmationDialog(string message)
|
||||
|
@ -6,43 +6,44 @@
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.Dialogs.WhyCacheThoughDialog">
|
||||
<Grid RowDefinitions="AUTO,AUTO,AUTO,*,AUTO" ColumnDefinitions="*,AUTO"
|
||||
Background="{StaticResource SPT_Background_Light}">
|
||||
<Grid RowDefinitions="AUTO,AUTO,AUTO,*,AUTO" ColumnDefinitions="*,AUTO, AUTO"
|
||||
Background="{StaticResource AKI_Background_Light}">
|
||||
<Label Content="What is the installer cache for?" FontSize="20"
|
||||
Foreground="{StaticResource SPT_Brush_Yellow}" />
|
||||
Foreground="{StaticResource AKI_Brush_Yellow}"
|
||||
/>
|
||||
<TextBlock Grid.Row="1" Grid.ColumnSpan="2" TextWrapping="Wrap" xml:space="preserve">
|
||||
The installer cache is used to ensure you don't re-download large files that you've already downloaded before.
|
||||
<Span Foreground="red">You should only delete the cache folder if</Span>
|
||||
- You are low on space
|
||||
or
|
||||
- You are not planning on installing SPT again any time soon
|
||||
|
||||
If possible, you should leave the cache in place to avoid uneccessary, lengthy downloads.
|
||||
It also helps us prevent extra traffic to our limited download mirrors. Every bit helps <Span Foreground="red"
|
||||
FontSize="25">♥️</Span>
|
||||
It also helps us prevent extra traffic to our limited download mirrors. Every bit helps <Span Foreground="red" FontSize="25">♥️</Span>
|
||||
</TextBlock>
|
||||
<Label Grid.Row="2" Content="You can find the cache folder here" />
|
||||
<Button Grid.Row="3" Grid.ColumnSpan="2"
|
||||
Content="{Binding Source={x:Static helpers:DownloadCacheHelper.CachePath}}"
|
||||
<Label Grid.Row="2" Content="You can find the cache folder here"
|
||||
/>
|
||||
<Button Grid.Row="3" Grid.ColumnSpan="2" Content="{Binding Source={x:Static helpers:DownloadCacheHelper.CachePath}}"
|
||||
Classes="link"
|
||||
Margin="0 10"
|
||||
IsVisible="{Binding CacheExists, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding OpenCacheFolder, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
Command="{Binding OpenCacheFolder, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
<Label Grid.Row="3" Content="No cache folder exists"
|
||||
IsVisible="{Binding !CacheExists, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
IsVisible="{Binding !CacheExists, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
<Label Grid.Row="4"
|
||||
Content="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=AdditionalInfo}"
|
||||
Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=AdditionalInfoColor}" />
|
||||
Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=AdditionalInfoColor}"
|
||||
/>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="4" Grid.Column="1" Spacing="10">
|
||||
<Button Content="Move Downloaded Patcher"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=MoveDownloadsPatcherToCache}" />
|
||||
<Button Grid.Row="4" Grid.Column="1" Content="Move Downloaded Patcher" Margin="0 0 10 0"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=MoveDownloadsPatcherToCache}"
|
||||
/>
|
||||
|
||||
<Button Content="Clear Metadata Cache"
|
||||
Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=ClearCachedMetaData}"
|
||||
/>
|
||||
|
||||
<Button Content="Close" Classes="yellow"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dialogHost:DialogHost}, Path=CloseDialogCommand}" />
|
||||
</StackPanel>
|
||||
<Button Grid.Row="4" Grid.Column="2" Content="Close" Classes="yellow"
|
||||
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=dialogHost:DialogHost}, Path=CloseDialogCommand}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -3,13 +3,9 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Serilog;
|
||||
using SPTInstaller.Models;
|
||||
using Color = System.Drawing.Color;
|
||||
|
||||
namespace SPTInstaller.CustomControls.Dialogs;
|
||||
|
||||
public partial class WhyCacheThoughDialog : UserControl
|
||||
{
|
||||
private int _movePatcherState = 0;
|
||||
@ -49,64 +45,19 @@ public partial class WhyCacheThoughDialog : UserControl
|
||||
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = Path.EndsInDirectorySeparator(DownloadCacheHelper.CachePath)
|
||||
? DownloadCacheHelper.CachePath
|
||||
: DownloadCacheHelper.CachePath + Path.DirectorySeparatorChar,
|
||||
FileName = Path.EndsInDirectorySeparator(DownloadCacheHelper.CachePath) ? DownloadCacheHelper.CachePath : DownloadCacheHelper.CachePath + Path.DirectorySeparatorChar,
|
||||
UseShellExecute = true,
|
||||
Verb = "open"
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearCachedMetaData()
|
||||
{
|
||||
var cachedMetadata =
|
||||
new DirectoryInfo(DownloadCacheHelper.CachePath).GetFiles("*.json", SearchOption.TopDirectoryOnly);
|
||||
|
||||
var message = "no cached metadata to remove";
|
||||
|
||||
if (cachedMetadata.Length == 0)
|
||||
{
|
||||
AdditionalInfo = message;
|
||||
AdditionalInfoColor = "dodgerblue";
|
||||
Log.Information(message);
|
||||
return;
|
||||
}
|
||||
|
||||
var allDeleted = true;
|
||||
|
||||
foreach (var file in cachedMetadata)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
file.Refresh();
|
||||
if (file.Exists)
|
||||
{
|
||||
allDeleted = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to delete cached metadata file: {file.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
message = allDeleted ? "cached metadata removed" : "some files could not be removed. Check logs";
|
||||
AdditionalInfo = message;
|
||||
AdditionalInfoColor = allDeleted ? "green" : "red";
|
||||
Log.Information(message);
|
||||
|
||||
var data = ServiceHelper.Get<InternalData>();
|
||||
|
||||
App.ReLaunch(false, data.TargetInstallPath!);
|
||||
}
|
||||
|
||||
public void MoveDownloadsPatcherToCache()
|
||||
{
|
||||
switch (_movePatcherState)
|
||||
{
|
||||
case 0:
|
||||
var downloadsPath = KnownFolders.GetPath(KnownFolder.Downloads);
|
||||
var downloadsPath =
|
||||
Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
|
||||
|
||||
var downloadsFolder = new DirectoryInfo(downloadsPath);
|
||||
|
||||
@ -120,8 +71,7 @@ public partial class WhyCacheThoughDialog : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
_foundPatcher = downloadsFolder.GetFiles("Patcher_*").OrderByDescending(p => p.CreationTime)
|
||||
.FirstOrDefault();
|
||||
_foundPatcher = downloadsFolder.GetFiles("Patcher_*").OrderByDescending(p => p.CreationTime).FirstOrDefault();
|
||||
|
||||
if (_foundPatcher == null || !_foundPatcher.Exists)
|
||||
{
|
||||
@ -134,8 +84,7 @@ public partial class WhyCacheThoughDialog : UserControl
|
||||
}
|
||||
|
||||
Log.Information($"[MV_0] Found patcher for move: {_foundPatcher.Name}");
|
||||
AdditionalInfo =
|
||||
$"Click again to move the below patcher file to the cache folder\n{_foundPatcher?.Name ?? "-SOMETHING WENT WRONG-"}";
|
||||
AdditionalInfo = $"Click again to move the below patcher file to the cache folder\n{_foundPatcher?.Name ?? "-SOMETHING WENT WRONG-"}";
|
||||
AdditionalInfoColor = "#FFC107";
|
||||
_movePatcherState = 1;
|
||||
break;
|
||||
@ -155,7 +104,6 @@ public partial class WhyCacheThoughDialog : UserControl
|
||||
AdditionalInfoColor = "red";
|
||||
Log.Error(ex, "Failed to move downloaded patcher file into cache");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Log.Error("[MV_ ] Move state is broken :(");
|
||||
|
@ -55,11 +55,10 @@ public class DistributedSpacePanel : Panel
|
||||
.WithWidth(finalSize.Width);
|
||||
|
||||
|
||||
|
||||
child.Arrange(rcChild);
|
||||
continue;
|
||||
}
|
||||
|
||||
;
|
||||
};
|
||||
|
||||
rcChild = rcChild.WithY(rcChild.Y + previousChildSize);
|
||||
previousChildSize = child.DesiredSize.Height;
|
||||
|
@ -8,14 +8,15 @@
|
||||
x:Class="SPTInstaller.CustomControls.PreCheckDetails">
|
||||
|
||||
<UserControl.Resources>
|
||||
<cvt:StateSpinnerStateToColorConverter x:Key="colorConverter" />
|
||||
<cvt:StateSpinnerStateToColorConverter x:Key="colorConverter"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Panel>
|
||||
<!-- show when nothing is selected -->
|
||||
<Label Content="Select a Pre-Check to see more info" FontSize="20"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
IsVisible="{Binding HasSelection, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={x:Static BoolConverters.Not}}" />
|
||||
IsVisible="{Binding HasSelection, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={x:Static BoolConverters.Not}}"
|
||||
/>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding PreChecks}" VerticalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
@ -26,19 +27,20 @@
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="model:PreCheckBase">
|
||||
<!-- selected precheck details -->
|
||||
<Grid RowDefinitions="10, *, Auto, 10" ColumnDefinitions="10, 10, *, 10"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding IsSelected}">
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Width="3"
|
||||
Fill="{Binding State, Converter={StaticResource colorConverter}}"
|
||||
HorizontalAlignment="Left" />
|
||||
<Grid RowDefinitions="10, *, Auto, 10" ColumnDefinitions="10, 10, *, 10" VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding IsSelected}"
|
||||
>
|
||||
<Rectangle Grid.Row="1" Grid.Column="1" Width="3" Fill="{Binding State, Converter={StaticResource colorConverter}}"
|
||||
HorizontalAlignment="Left"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.Column="2" HorizontalAlignment="Left">
|
||||
<Label Content="{Binding Name}"
|
||||
FontSize="20" />
|
||||
<Rectangle Height="1" Fill="Gray" Margin="0 10" />
|
||||
FontSize="20"
|
||||
/>
|
||||
<Rectangle Height="1" Fill="Gray" Margin="0 10"/>
|
||||
<TextBlock Text="{Binding PreCheckDetails}"
|
||||
TextWrapping="Wrap" />
|
||||
TextWrapping="Wrap"
|
||||
/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Classes="yellow"
|
||||
@ -48,7 +50,8 @@
|
||||
Command="{Binding ActionButtonCommand}"
|
||||
Content="{Binding ActionButtonText}"
|
||||
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
|
||||
HorizontalAlignment="Stretch" />
|
||||
HorizontalAlignment="Stretch"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
@ -5,56 +5,59 @@
|
||||
xmlns:cc="using:SPTInstaller.CustomControls"
|
||||
xmlns:convt="using:SPTInstaller.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.PreCheckItem">
|
||||
x:Class="SPTInstaller.CustomControls.PreCheckItem"
|
||||
>
|
||||
|
||||
<UserControl.Resources>
|
||||
<convt:StatusSpinnerIsStateConverter x:Key="IsStateConverter" />
|
||||
<convt:StatusSpinnerIsStateConverter x:Key="IsStateConverter"/>
|
||||
</UserControl.Resources>
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Label.bold">
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Style>
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Label.bold">
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selectable">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Foreground" Value="{StaticResource SPT_Background_Dark}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AKI_Background_Dark}"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selectable:pointerover">
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selectable:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selectable:disabled /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="{StaticResource SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource AKI_Brush_DarkGrayBlue}"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.selected">
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Brush_LightGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Background" Value="{StaticResource AKI_Brush_LightGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Button CornerRadius="15" Padding="0" Margin="0" HorizontalAlignment="Stretch" VerticalContentAlignment="Center"
|
||||
Command="{Binding SelectCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CommandParameter="{Binding }"
|
||||
Classes.selectable="{Binding !IsSelected, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.selected="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=UserControl}}">
|
||||
Classes.selected="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
>
|
||||
<Grid ColumnDefinitions="AUTO, AUTO" Margin="3 0 0 3">
|
||||
<cc:StatusSpinner State="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
<cc:StatusSpinner State="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||
|
||||
<Label Grid.Column="1"
|
||||
Content="{Binding PreCheckName, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.bold="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
<Label Grid.Column="1"
|
||||
Content="{Binding PreCheckName, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.bold="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource ResourceKey=IsStateConverter},
|
||||
ConverterParameter=Running}" />
|
||||
ConverterParameter=Running}"
|
||||
/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</UserControl>
|
@ -2,83 +2,78 @@
|
||||
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"
|
||||
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" IterationCount="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>
|
||||
<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" IterationCount="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, *">
|
||||
<!-- 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 SPT_Background_Dark}"
|
||||
HorizontalAlignment="Left"
|
||||
Classes.running="{Binding IsRunning, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.completed="{Binding IsCompleted, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
<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}}" />
|
||||
<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>
|
||||
<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>
|
@ -2,14 +2,15 @@
|
||||
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"
|
||||
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>
|
||||
x:Class="SPTInstaller.CustomControls.ProgressableTaskList"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<convt:InvertedProgressConverter x:Key="invtProgressConvt"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<ProgressBar Orientation="Vertical"
|
||||
@ -18,13 +19,13 @@
|
||||
Foreground="{Binding PendingColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="25 20" />
|
||||
<ItemsControl Name="itemsControl"
|
||||
ItemsSource="{Binding Tasks, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Padding="5">
|
||||
Margin="25 20"
|
||||
/>
|
||||
<ItemsControl Name="itemsControl" ItemsSource="{Binding Tasks, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Padding="5">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<cc:DistributedSpacePanel />
|
||||
<cc:DistributedSpacePanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
@ -35,7 +36,8 @@
|
||||
IsCompleted="{Binding IsCompleted}"
|
||||
PendingColor="{Binding PendingColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
RunningColor="{Binding RunningColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CompletedColor="{Binding CompletedColor, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
CompletedColor="{Binding CompletedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
@ -20,7 +20,6 @@ public partial class ProgressableTaskList : UserControl
|
||||
}
|
||||
|
||||
private int _taskProgress;
|
||||
|
||||
public int TaskProgress
|
||||
{
|
||||
get => _taskProgress;
|
||||
@ -74,7 +73,7 @@ public partial class ProgressableTaskList : UserControl
|
||||
|
||||
var progress = (int)Math.Floor((double)completedTasks / (Tasks.Count - 1) * 100);
|
||||
|
||||
for (; TaskProgress < progress;)
|
||||
for(; TaskProgress < progress;)
|
||||
{
|
||||
TaskProgress += 1;
|
||||
await Task.Delay(1);
|
||||
|
@ -4,44 +4,44 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:convt="using:SPTInstaller.Converters"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.StatusSpinner">
|
||||
x:Class="SPTInstaller.CustomControls.StatusSpinner"
|
||||
>
|
||||
|
||||
<UserControl.Resources>
|
||||
<convt:StatusSpinnerIsStateConverter x:Key="IsStateConverter" />
|
||||
<convt:StatusSpinnerIsProcessingConverter x:Key="IsInProcessingStateConverter" />
|
||||
<convt:StatusSpinnerIsStateConverter x:Key="IsStateConverter"/>
|
||||
<convt:StatusSpinnerIsProcessingConverter x:Key="IsInProcessingStateConverter"/>
|
||||
</UserControl.Resources>
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Arc.running">
|
||||
<Setter Property="Stroke" Value="DodgerBlue" />
|
||||
<Setter Property="Stroke" Value="DodgerBlue"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.ok">
|
||||
<Setter Property="Data" Value="{StaticResource CircledCheck}" />
|
||||
<Setter Property="Fill" Value="Green" />
|
||||
<Setter Property="Data" Value="{StaticResource CircledCheck}"/>
|
||||
<Setter Property="Fill" Value="Green"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.warning">
|
||||
<Setter Property="Data" Value="{StaticResource CircledWarn}" />
|
||||
<Setter Property="Fill" Value="Goldenrod" />
|
||||
<Setter Property="Data" Value="{StaticResource CircledWarn}"/>
|
||||
<Setter Property="Fill" Value="Goldenrod"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Path.error">
|
||||
<Setter Property="Data" Value="{StaticResource CircledX}" />
|
||||
<Setter Property="Fill" Value="Red" />
|
||||
<Setter Property="Data" Value="{StaticResource CircledX}"/>
|
||||
<Setter Property="Fill" Value="Red"/>
|
||||
</Style>
|
||||
|
||||
<Style Selector="Arc">
|
||||
<Setter Property="Stroke" Value="Gray" />
|
||||
<Setter Property="IsVisible"
|
||||
Value="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource ResourceKey=IsInProcessingStateConverter}}" />
|
||||
<Setter Property="Stroke" Value="Gray"/>
|
||||
<Setter Property="IsVisible" Value="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource ResourceKey=IsInProcessingStateConverter}}"/>
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:1" IterationCount="Infinite">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="RotateTransform.Angle" Value="0" />
|
||||
<Setter Property="RotateTransform.Angle" Value="0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="RotateTransform.Angle" Value="360" />
|
||||
<Setter Property="RotateTransform.Angle" Value="360"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
@ -50,9 +50,10 @@
|
||||
|
||||
<Grid>
|
||||
<Canvas Margin="0 3 0 0" Height="20" Width="20"
|
||||
IsVisible="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
IsVisible="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource ResourceKey=IsInProcessingStateConverter}, ConverterParameter=invert}">
|
||||
<Ellipse Fill="White" Height="15" Width="15" Canvas.Top="3" Canvas.Left="3" />
|
||||
<Ellipse Fill="White" Height="15" Width="15" Canvas.Top="3" Canvas.Left="3"
|
||||
/>
|
||||
<Path StrokeThickness="2"
|
||||
Classes.ok="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource ResourceKey=IsStateConverter},
|
||||
@ -62,13 +63,15 @@
|
||||
ConverterParameter=Warning}"
|
||||
Classes.error="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource ResourceKey=IsStateConverter},
|
||||
ConverterParameter=Error}" />
|
||||
ConverterParameter=Error}"
|
||||
/>
|
||||
</Canvas>
|
||||
|
||||
<Arc StartAngle="280" SweepAngle="80" Margin="0 3 0 0" StrokeThickness="3"
|
||||
Width="20" Height="20" VerticalAlignment="Top"
|
||||
Classes.running="{Binding State, RelativeSource={RelativeSource AncestorType=UserControl},
|
||||
Converter={StaticResource ResourceKey=IsStateConverter},
|
||||
ConverterParameter=Running}" />
|
||||
ConverterParameter=Running}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -10,9 +10,9 @@ public partial class StatusSpinner : ReactiveUserControl<UserControl>
|
||||
{
|
||||
Pending = -1,
|
||||
Running = 0,
|
||||
OK = 1,
|
||||
OK = 1,
|
||||
Warning = 2,
|
||||
Error = 3,
|
||||
Error = 3,
|
||||
}
|
||||
|
||||
public StatusSpinner()
|
||||
|
@ -4,33 +4,37 @@
|
||||
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">
|
||||
<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}}" />
|
||||
<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" />
|
||||
<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">
|
||||
<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"
|
||||
IsIndeterminate="{Binding IndeterminateProgress, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
<ProgressBar IsVisible="{Binding ShowProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Value="{Binding Progress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsIndeterminate="{Binding IndeterminateProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
<Label Grid.Column="1"
|
||||
Content="{Binding Progress, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat='{}{0}%'}"
|
||||
IsVisible="{Binding !IndeterminateProgress, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Label Grid.Column="1"
|
||||
Content="{Binding Progress, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat='{}{0}%'}"
|
||||
IsVisible="{Binding !IndeterminateProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -5,68 +5,72 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.TitleBar">
|
||||
|
||||
<Grid ColumnDefinitions="AUTO,*,AUTO,AUTO">
|
||||
<Grid ColumnDefinitions="AUTO,*,AUTO,AUTO">
|
||||
|
||||
<Rectangle Grid.ColumnSpan="6" IsHitTestVisible="False"
|
||||
Fill="{Binding Background, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}" />
|
||||
<Rectangle Grid.ColumnSpan="6" IsHitTestVisible="False"
|
||||
Fill="{Binding Background, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
<Label Content="{Binding Title, RelativeSource={
|
||||
<Label Content="{Binding Title, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
IsHitTestVisible="False"
|
||||
Foreground="{Binding Foreground, RelativeSource={
|
||||
IsHitTestVisible="False"
|
||||
Foreground="{Binding Foreground, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Background="Transparent"
|
||||
VerticalContentAlignment="Center" />
|
||||
Background="Transparent"
|
||||
VerticalContentAlignment="Center"
|
||||
/>
|
||||
|
||||
<!-- Minimize (-) Button -->
|
||||
<Button Content="" Grid.Column="2"
|
||||
Foreground="{Binding ButtonForeground, RelativeSource={
|
||||
<!-- Minimize (-) Button -->
|
||||
<Button Content="" Grid.Column="2"
|
||||
Foreground="{Binding ButtonForeground, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding MinButtonCommand, RelativeSource={
|
||||
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 SPT_Brush_DarkGrayBlue}" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
</Style>
|
||||
<Style Selector="Button:pressed /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource SPT_Background_Light}" />
|
||||
</Style>
|
||||
</Button.Styles>
|
||||
</Button>
|
||||
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={
|
||||
<!-- Close (X) Button -->
|
||||
<Button Content="" Grid.Column="3"
|
||||
Foreground="{Binding ButtonForeground, RelativeSource={
|
||||
RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding XButtonCommand, RelativeSource={
|
||||
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>
|
||||
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>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
@ -5,42 +5,43 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.CustomControls.UpdateButton">
|
||||
<UserControl.Styles>
|
||||
<StyleInclude Source="../Assets/Styles.axaml" />
|
||||
<StyleInclude Source="../Assets/Styles.axaml"/>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Panel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="!Updating" RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
<Binding Path="UpdateAvailable" RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="{Binding InfoText, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CornerRadius="20 0 0 20"
|
||||
Classes="yellow"
|
||||
Command="{Binding UpdateCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="!Updating" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
|
||||
<Binding Path="UpdateAvailable" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
|
||||
</MultiBinding>
|
||||
</StackPanel.IsVisible>
|
||||
<Button Content="{Binding InfoText, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
CornerRadius="20 0 0 20"
|
||||
Classes="yellow"
|
||||
Command="{Binding UpdateCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
<Button Content="Not now" CornerRadius="0 20 20 0"
|
||||
Command="{Binding DismissCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Button Content="Not now" CornerRadius="0 20 20 0"
|
||||
Command="{Binding DismissCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
</StackPanel>
|
||||
|
||||
<Panel Margin="0 10">
|
||||
<Panel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.Or}">
|
||||
<Binding Path="Updating" RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
<Binding Path="CheckingForUpdate" RelativeSource="{RelativeSource AncestorType=UserControl}" />
|
||||
</MultiBinding>
|
||||
</Panel.IsVisible>
|
||||
<ProgressBar CornerRadius="20" VerticalAlignment="Stretch"
|
||||
Value="{Binding DownloadProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
IsIndeterminate="{Binding IsIndeterminate, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||
<Label Content="{Binding InfoText, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Foreground="Black" FontWeight="SemiBold" />
|
||||
</Panel>
|
||||
<Panel Margin="0 10">
|
||||
<Panel.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.Or}">
|
||||
<Binding Path="Updating" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
|
||||
<Binding Path="CheckingForUpdate" RelativeSource="{RelativeSource AncestorType=UserControl}"/>
|
||||
</MultiBinding>
|
||||
</Panel.IsVisible>
|
||||
<ProgressBar CornerRadius="20" VerticalAlignment="Stretch"
|
||||
Value="{Binding DownloadProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
IsIndeterminate="{Binding IsIndeterminate, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
<Label Content="{Binding InfoText, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Foreground="Black" FontWeight="SemiBold"/>
|
||||
</Panel>
|
||||
</Panel>
|
||||
</UserControl>
|
@ -20,9 +20,8 @@ public partial class UpdateButton : UserControl
|
||||
set => SetValue(InfoTextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> CheckingForUpdateProperty =
|
||||
AvaloniaProperty.Register<UpdateButton, bool>(
|
||||
"CheckingForUpdate");
|
||||
public static readonly StyledProperty<bool> CheckingForUpdateProperty = AvaloniaProperty.Register<UpdateButton, bool>(
|
||||
"CheckingForUpdate");
|
||||
|
||||
public bool CheckingForUpdate
|
||||
{
|
||||
@ -30,9 +29,8 @@ public partial class UpdateButton : UserControl
|
||||
set => SetValue(CheckingForUpdateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ICommand> DismissCommandProperty =
|
||||
AvaloniaProperty.Register<UpdateButton, ICommand>(
|
||||
"DismissCommand");
|
||||
public static readonly StyledProperty<ICommand> DismissCommandProperty = AvaloniaProperty.Register<UpdateButton, ICommand>(
|
||||
"DismissCommand");
|
||||
|
||||
public ICommand DismissCommand
|
||||
{
|
||||
@ -40,9 +38,8 @@ public partial class UpdateButton : UserControl
|
||||
set => SetValue(DismissCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<ICommand> UpdateCommandProperty =
|
||||
AvaloniaProperty.Register<UpdateButton, ICommand>(
|
||||
"UpdateCommand");
|
||||
public static readonly StyledProperty<ICommand> UpdateCommandProperty = AvaloniaProperty.Register<UpdateButton, ICommand>(
|
||||
"UpdateCommand");
|
||||
|
||||
public ICommand UpdateCommand
|
||||
{
|
||||
|
65
SPTInstaller/CustomControls/UpdateInfoCard.axaml
Normal file
65
SPTInstaller/CustomControls/UpdateInfoCard.axaml
Normal file
@ -0,0 +1,65 @@
|
||||
<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.UpdateInfoCard"
|
||||
MinHeight="100" MinWidth="300">
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Grid">
|
||||
<Setter Property="Opacity" Value="0"/>
|
||||
<Setter Property="Transitions">
|
||||
<Setter.Value>
|
||||
<Transitions>
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.2"/>
|
||||
</Transitions>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style Selector="Grid.show">
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
<Style Selector="ProgressBar">
|
||||
<Setter Property="IsVisible" Value="False"/>
|
||||
</Style>
|
||||
<Style Selector="ProgressBar.checking">
|
||||
<Setter Property="IsIndeterminate" Value="True"/>
|
||||
<Setter Property="IsVisible" Value="True"/>
|
||||
</Style>
|
||||
<Style Selector="ProgressBar.updating">
|
||||
<Setter Property="IsIndeterminate" Value="false"/>
|
||||
<Setter Property="IsVisible" Value="True"/>
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="10,*,AUTO,AUTO,10" RowDefinitions="10,AUTO,AUTO,10"
|
||||
Classes.show="{Binding ShowUpdateCard, RelativeSource={RelativeSource AncestorType=UserControl}}">
|
||||
<Border Grid.ColumnSpan="5" Grid.RowSpan="4" Background="{StaticResource AKI_Background_Light}"
|
||||
BoxShadow="2 2 10 .1 black" CornerRadius="8"
|
||||
/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="3" MaxWidth="400"
|
||||
Text="{Binding InfoText, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
TextWrapping="Wrap" Margin="0 10"
|
||||
/>
|
||||
|
||||
<Button Grid.Column="2" Grid.Row="2" Content="Not now"
|
||||
Classes="outlined"
|
||||
IsVisible="{Binding UpdateAvailable, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding NotNowCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
<Button Grid.Column="3" Grid.Row="2" Content="Update"
|
||||
Classes="yellow" Margin="10 0 0 0"
|
||||
IsVisible="{Binding UpdateAvailable, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Command="{Binding UpdateInstallerCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
<ProgressBar Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="3"
|
||||
Value="{Binding DownloadProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.updating="{Binding Updating, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
Classes.checking="{Binding IndeterminateProgress, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
76
SPTInstaller/CustomControls/UpdateInfoCard.axaml.cs
Normal file
76
SPTInstaller/CustomControls/UpdateInfoCard.axaml.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace SPTInstaller.CustomControls;
|
||||
public partial class UpdateInfoCard : UserControl
|
||||
{
|
||||
public UpdateInfoCard()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public bool ShowUpdateCard
|
||||
{
|
||||
get => GetValue(ShowUpdateCardProperty);
|
||||
set => SetValue(ShowUpdateCardProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<bool> ShowUpdateCardProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(ShowUpdateCard));
|
||||
|
||||
public bool Updating
|
||||
{
|
||||
get => GetValue(UpdatingProperty);
|
||||
set => SetValue(UpdatingProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<bool> UpdatingProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(Updating));
|
||||
|
||||
public bool UpdateAvailable
|
||||
{
|
||||
get => GetValue(UpdateAvailableProperty);
|
||||
set => SetValue(UpdateAvailableProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<bool> UpdateAvailableProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(UpdateAvailable));
|
||||
|
||||
public bool IndeterminateProgress
|
||||
{
|
||||
get => GetValue(IndeterminateProgressProperty);
|
||||
set => SetValue(IndeterminateProgressProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<bool> IndeterminateProgressProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, bool>(nameof(IndeterminateProgress));
|
||||
|
||||
public string InfoText
|
||||
{
|
||||
get => GetValue(InfoTextProperty);
|
||||
set => SetValue(InfoTextProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<string> InfoTextProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, string>(nameof(InfoText));
|
||||
|
||||
public int DownloadProgress
|
||||
{
|
||||
get => GetValue(DownloadProgressProperty);
|
||||
set => SetValue(DownloadProgressProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<int> DownloadProgressProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, int>(nameof(DownloadProgress));
|
||||
|
||||
public ICommand NotNowCommand
|
||||
{
|
||||
get => GetValue(NotNowCommandProperty);
|
||||
set => SetValue(NotNowCommandProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<ICommand> NotNowCommandProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, ICommand>(nameof(NotNowCommand));
|
||||
|
||||
public ICommand UpdateInstallerCommand
|
||||
{
|
||||
get => GetValue(UpdateInstallerCommandProperty);
|
||||
set => SetValue(UpdateInstallerCommandProperty, value);
|
||||
}
|
||||
public static readonly StyledProperty<ICommand> UpdateInstallerCommandProperty =
|
||||
AvaloniaProperty.Register<UpdateInfoCard, ICommand>(nameof(UpdateInstallerCommand));
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
// Global using directives
|
||||
|
||||
global using System;
|
||||
global using System.IO;
|
@ -8,7 +8,7 @@ public static class DirectorySizeHelper
|
||||
// SizeSuffix implementation found here:
|
||||
// https://stackoverflow.com/a/14488941
|
||||
static readonly string[] SizeSuffixes =
|
||||
{ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
|
||||
{ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
|
||||
|
||||
public static string SizeSuffix(Int64 value, int decimalPlaces = 1)
|
||||
{
|
||||
|
@ -1,21 +1,15 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class DownloadCacheHelper
|
||||
{
|
||||
private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromMinutes(15) };
|
||||
private static HttpClient _httpClient = new() { Timeout = TimeSpan.FromHours(1) };
|
||||
|
||||
public static TimeSpan SuggestedTtl = TimeSpan.FromHours(1);
|
||||
public static string CachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"spt-installer/cache");
|
||||
|
||||
public static string ReleaseMirrorUrl = "https://spt-releases.modd.in/release.json";
|
||||
public static string PatchMirrorUrl = "https://slugma.waffle-lord.net/mirrors.json";
|
||||
public static string InstallerUrl = "https://ligma.waffle-lord.net/SPTInstaller.exe";
|
||||
public static string InstallerInfoUrl = "https://ligma.waffle-lord.net/installer.json";
|
||||
public static string CachePath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "spt-installer/cache");
|
||||
|
||||
public static string GetCacheSizeText()
|
||||
{
|
||||
@ -50,10 +44,10 @@ public static class DownloadCacheHelper
|
||||
/// <param name="expectedHash">The expected hash of the file in the cache</param>
|
||||
/// <param name="cachedFile">The file found in the cache; null if no file is found</param>
|
||||
/// <returns>True if the file is in the cache and its hash matches the expected hash, otherwise false</returns>
|
||||
public static bool CheckCacheHash(string fileName, string expectedHash, out FileInfo cachedFile)
|
||||
=> CheckCacheHash(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
|
||||
public static bool CheckCache(string fileName, string expectedHash, out FileInfo cachedFile)
|
||||
=> CheckCache(new FileInfo(Path.Join(CachePath, fileName)), expectedHash, out cachedFile);
|
||||
|
||||
private static bool CheckCacheHash(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
|
||||
private static bool CheckCache(FileInfo cacheFile, string expectedHash, out FileInfo fileInCache)
|
||||
{
|
||||
fileInCache = cacheFile;
|
||||
|
||||
@ -79,45 +73,7 @@ public static class DownloadCacheHelper
|
||||
Log.Warning("Hashes DO NOT MATCH");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Something went wrong during hashing");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a file in the cache based on a time-to-live from its last modified time
|
||||
/// </summary>
|
||||
/// <param name="fileName">The name of the file to look for in the cache</param>
|
||||
/// <param name="ttl">The time-to-live to check against</param>
|
||||
/// <param name="cachedFile">The file found in the cache if it exists</param>
|
||||
/// <returns>Returns true if the file was found in the cache, otherwise false</returns>
|
||||
public static bool CheckCacheTTL(string fileName, TimeSpan ttl, out FileInfo cachedFile) =>
|
||||
CheckCacheTTL(new FileInfo(Path.Join(CachePath, fileName)), ttl, out cachedFile);
|
||||
|
||||
private static bool CheckCacheTTL(FileInfo cacheFile, TimeSpan ttl, out FileInfo fileInCache)
|
||||
{
|
||||
fileInCache = cacheFile;
|
||||
|
||||
try
|
||||
{
|
||||
cacheFile.Refresh();
|
||||
Directory.CreateDirectory(CachePath);
|
||||
|
||||
if (!cacheFile.Exists)
|
||||
{
|
||||
Log.Information($"{cacheFile.Name} {(cacheFile.Exists ? "is in cache" : "NOT in cache")}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var validTimeToLive = cacheFile.LastWriteTime.Add(ttl) > DateTime.Now;
|
||||
|
||||
Log.Information($"{cacheFile.Name} TTL is {(validTimeToLive ? "OK" : "INVALID")}");
|
||||
|
||||
return validTimeToLive;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Something went wrong during hashing");
|
||||
return false;
|
||||
@ -132,8 +88,7 @@ public static class DownloadCacheHelper
|
||||
/// <param name="progress">A provider for progress updates</param>
|
||||
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
|
||||
/// <remarks>If the file exists, it is deleted before downloading</remarks>
|
||||
public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink,
|
||||
IProgress<double> progress)
|
||||
public static async Task<FileInfo?> DownloadFileAsync(string outputFileName, string targetLink, IProgress<double> progress)
|
||||
{
|
||||
Directory.CreateDirectory(CachePath);
|
||||
var outputFile = new FileInfo(Path.Join(CachePath, outputFileName));
|
||||
@ -145,20 +100,7 @@ public static class DownloadCacheHelper
|
||||
|
||||
// Use the provided extension method
|
||||
using (var file = new FileStream(outputFile.FullName, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
if (!await _httpClient.DownloadDataAsync(targetLink, file, progress))
|
||||
{
|
||||
Log.Error($"Download failed: {targetLink}");
|
||||
|
||||
outputFile.Refresh();
|
||||
|
||||
if (outputFile.Exists)
|
||||
{
|
||||
outputFile.Delete();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
await _httpClient.DownloadDataAsync(targetLink, file, progress);
|
||||
|
||||
outputFile.Refresh();
|
||||
|
||||
@ -211,41 +153,13 @@ public static class DownloadCacheHelper
|
||||
|
||||
return outputFile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to download file from stream: {fileName}", outputFileName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or download a file using a time to live
|
||||
/// </summary>
|
||||
/// <param name="fileName">The file to get from cache</param>
|
||||
/// <param name="targetLink">The link to use for the download</param>
|
||||
/// <param name="progress">A progress object for reporting download progress</param>
|
||||
/// <param name="timeToLive">The time-to-live to check against in the cache</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink,
|
||||
IProgress<double> progress, TimeSpan timeToLive)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CheckCacheTTL(fileName, timeToLive, out FileInfo cachedFile))
|
||||
{
|
||||
return cachedFile;
|
||||
}
|
||||
|
||||
Log.Information($"Downloading File: {targetLink}");
|
||||
return await DownloadFileAsync(fileName, targetLink, progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error while getting file: {fileName}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the file from cache or download it
|
||||
/// </summary>
|
||||
@ -255,15 +169,13 @@ public static class DownloadCacheHelper
|
||||
/// <param name="expectedHash">The expected hash of the cached file</param>
|
||||
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
|
||||
/// <remarks>Use <see cref="DownloadFileAsync(string, string, IProgress{double})"/> if you don't have an expected cache file hash</remarks>
|
||||
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink,
|
||||
IProgress<double> progress, string expectedHash)
|
||||
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, string targetLink, IProgress<double> progress, string expectedHash)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CheckCacheHash(fileName, expectedHash, out var cacheFile))
|
||||
if (CheckCache(fileName, expectedHash, out var cacheFile))
|
||||
return cacheFile;
|
||||
|
||||
Log.Information($"Downloading File: {targetLink}");
|
||||
return await DownloadFileAsync(fileName, targetLink, progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -281,12 +193,11 @@ public static class DownloadCacheHelper
|
||||
/// <param name="expectedHash">The expected hash of the cached file</param>
|
||||
/// <returns>A <see cref="FileInfo"/> object of the cached file</returns>
|
||||
/// <remarks>Use <see cref="DownloadFileAsync(string, Stream)"/> if you don't have an expected cache file hash</remarks>
|
||||
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream,
|
||||
string expectedHash)
|
||||
public static async Task<FileInfo?> GetOrDownloadFileAsync(string fileName, Stream fileDownloadStream, string expectedHash)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CheckCacheHash(fileName, expectedHash, out var cacheFile))
|
||||
if (CheckCache(fileName, expectedHash, out var cacheFile))
|
||||
return cacheFile;
|
||||
|
||||
return await DownloadFileAsync(fileName, fileDownloadStream);
|
||||
|
@ -1,22 +1,24 @@
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
using Gitea.Model;
|
||||
using Serilog;
|
||||
|
||||
namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class FileHashHelper
|
||||
{
|
||||
// public static string? GetGiteaReleaseHash(Release release)
|
||||
// {
|
||||
// var regex = Regex.Match(release.Body, @"Release Hash: (?<hash>\S+)");
|
||||
//
|
||||
// if (regex.Success)
|
||||
// {
|
||||
// return regex.Groups["hash"].Value;
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
public static string? GetGiteaReleaseHash(Release release)
|
||||
{
|
||||
var regex = Regex.Match(release.Body, @"Release Hash: (?<hash>\S+)");
|
||||
|
||||
if (regex.Success)
|
||||
{
|
||||
return regex.Groups["hash"].Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool CheckHash(FileInfo file, string expectedHash)
|
||||
{
|
||||
|
@ -9,6 +9,86 @@ namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class FileHelper
|
||||
{
|
||||
private static Result IterateDirectories(DirectoryInfo sourceDir, DirectoryInfo targetDir, string[] exclusions)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var dir in sourceDir.GetDirectories("*", SearchOption.AllDirectories))
|
||||
{
|
||||
var exclude = false;
|
||||
|
||||
foreach (var exclusion in exclusions)
|
||||
{
|
||||
var currentDirRelativePath = dir.FullName.Replace(sourceDir.FullName, "");
|
||||
|
||||
if (currentDirRelativePath.StartsWith(exclusion) || currentDirRelativePath == exclusion)
|
||||
{
|
||||
exclude = true;
|
||||
Log.Debug($"EXCLUSION FOUND :: DIR\nExclusion: '{exclusion}'\nPath: '{currentDirRelativePath}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (exclude)
|
||||
continue;
|
||||
|
||||
Directory.CreateDirectory(dir.FullName.Replace(sourceDir.FullName, targetDir.FullName));
|
||||
}
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while creating directories");
|
||||
return Result.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static Result IterateFiles(DirectoryInfo sourceDir, DirectoryInfo targetDir, string[] exclusions, Action<string, int> updateCallback = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
int totalFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories).Length;
|
||||
int processedFiles = 0;
|
||||
|
||||
foreach (var file in sourceDir.GetFiles("*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
var exclude = false;
|
||||
|
||||
updateCallback?.Invoke(file.Name, (int)Math.Floor(((double)processedFiles / totalFiles) * 100));
|
||||
|
||||
foreach (var exclusion in exclusions)
|
||||
{
|
||||
var currentFileRelativePath = file.FullName.Replace(sourceDir.FullName, "");
|
||||
|
||||
if (currentFileRelativePath.StartsWith(exclusion) || currentFileRelativePath == exclusion)
|
||||
{
|
||||
exclude = true;
|
||||
Log.Debug($"EXCLUSION FOUND :: FILE\nExclusion: '{exclusion}'\nPath: '{currentFileRelativePath}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (exclude)
|
||||
continue;
|
||||
|
||||
|
||||
var targetFile = file.FullName.Replace(sourceDir.FullName, targetDir.FullName);
|
||||
|
||||
Log.Debug($"COPY\nSourceDir: '{sourceDir.FullName}'\nTargetDir: '{targetDir.FullName}'\nNewPath: '{targetFile}'");
|
||||
|
||||
File.Copy(file.FullName, targetFile, true);
|
||||
processedFiles++;
|
||||
}
|
||||
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error while copying files");
|
||||
return Result.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetRedactedPath(string path)
|
||||
{
|
||||
var nameMatched = Regex.Match(path, @".:\\[uU]sers\\(?<NAME>[^\\]+)");
|
||||
@ -22,66 +102,20 @@ public static class FileHelper
|
||||
return path;
|
||||
}
|
||||
|
||||
public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir,
|
||||
IProgress<double> progress = null, string[] exclusions = null) =>
|
||||
public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, IProgress<double> progress = null, string[] exclusions = null) =>
|
||||
CopyDirectoryWithProgress(sourceDir, targetDir, (msg, prog) => progress?.Report(prog), exclusions);
|
||||
|
||||
public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir,
|
||||
Action<string, int> updateCallback = null, string[] exclusions = null)
|
||||
public static Result CopyDirectoryWithProgress(DirectoryInfo sourceDir, DirectoryInfo targetDir, Action<string, int> updateCallback = null, string[] exclusions = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var allFiles = sourceDir.GetFiles("*", SearchOption.AllDirectories);
|
||||
var fileCopies = new List<CopyInfo>();
|
||||
int count = 0;
|
||||
var iterateDirectoriesResult = IterateDirectories(sourceDir, targetDir, exclusions ??= new string[0]);
|
||||
|
||||
// filter files before starting copy
|
||||
foreach (var file in allFiles)
|
||||
{
|
||||
count++;
|
||||
updateCallback?.Invoke("getting list of files to copy", (int)Math.Floor((double)count / allFiles.Length * 100));
|
||||
if(!iterateDirectoriesResult.Succeeded) return iterateDirectoriesResult;
|
||||
|
||||
var currentFileRelativePath = file.FullName.Replace(sourceDir.FullName, "");
|
||||
var iterateFilesResult = IterateFiles(sourceDir, targetDir, exclusions ??= new string[0], updateCallback);
|
||||
|
||||
if (exclusions != null)
|
||||
{
|
||||
// check exclusions
|
||||
foreach (var exclusion in exclusions)
|
||||
{
|
||||
if (currentFileRelativePath.StartsWith(exclusion) || currentFileRelativePath == exclusion)
|
||||
{
|
||||
Log.Debug(
|
||||
$"EXCLUSION FOUND :: FILE\nExclusion: '{exclusion}'\nPath: '{currentFileRelativePath}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't copy .bak files
|
||||
if (currentFileRelativePath.EndsWith(".bak"))
|
||||
{
|
||||
Log.Debug($"EXCLUDING BAK FILE :: {currentFileRelativePath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
fileCopies.Add(new CopyInfo(file.FullName, file.FullName.Replace(sourceDir.FullName, targetDir.FullName)));
|
||||
}
|
||||
|
||||
count = 0;
|
||||
|
||||
// process copy info for files that need to be copied
|
||||
foreach (var copyInfo in fileCopies)
|
||||
{
|
||||
count++;
|
||||
updateCallback?.Invoke(copyInfo.FileName, (int)Math.Floor((double)count / fileCopies.Count * 100));
|
||||
|
||||
var result = copyInfo.Copy();
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (!iterateFilesResult.Succeeded) return iterateDirectoriesResult;
|
||||
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
@ -96,7 +130,6 @@ public static class FileHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug($"Starting StreamAssemblyResourceOut, resourcename: {resourceName}, outputFilePath: {outputFilePath}");
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
FileInfo outputFile = new FileInfo(outputFilePath);
|
||||
@ -120,7 +153,6 @@ public static class FileHelper
|
||||
}
|
||||
|
||||
outputFile.Refresh();
|
||||
|
||||
return outputFile.Exists;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -130,68 +162,61 @@ public static class FileHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a path is problematic
|
||||
/// </summary>
|
||||
/// <param name="path">The path the check</param>
|
||||
/// <param name="failedCheck">The check that failed</param>
|
||||
/// <returns>Returns true if the path is bad, otherwise false</returns>
|
||||
public static bool CheckPathForProblemLocations(string path, out PathCheck failedCheck)
|
||||
private enum PathCheckType
|
||||
{
|
||||
path = Path.TrimEndingDirectorySeparator(path);
|
||||
EndsWith = 0,
|
||||
Contains = 1,
|
||||
DriveRoot = 2
|
||||
}
|
||||
|
||||
failedCheck = new();
|
||||
public static bool CheckPathForProblemLocations(string path, out string detectedName)
|
||||
{
|
||||
detectedName = "";
|
||||
|
||||
var problemPaths = new List<PathCheck>()
|
||||
var problemNames = new Dictionary<string, PathCheckType>()
|
||||
{
|
||||
new("SteamApps", PathCheckType.EndsWith, PathCheckAction.Warn),
|
||||
new("Documents", PathCheckType.EndsWith, PathCheckAction.Warn),
|
||||
new("Desktop", PathCheckType.EndsWith, PathCheckAction.Deny),
|
||||
new("Battlestate Games", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("Desktop", PathCheckType.Contains, PathCheckAction.Warn),
|
||||
new("scoped_dir", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("Downloads", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("OneDrive", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("NextCloud", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("DropBox", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("Google", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("Program Files", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("Program Files (x86", PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new(Path.Join("spt-installer", "cache"), PathCheckType.Contains, PathCheckAction.Deny),
|
||||
new("Drive Root", PathCheckType.DriveRoot, PathCheckAction.Deny)
|
||||
{ "Documents", PathCheckType.EndsWith },
|
||||
{ "Desktop", PathCheckType.Contains },
|
||||
{ "scoped_dir", PathCheckType.Contains },
|
||||
{ "Downloads", PathCheckType.Contains },
|
||||
{ "OneDrive", PathCheckType.Contains },
|
||||
{ "NextCloud", PathCheckType.Contains },
|
||||
{ "DropBox", PathCheckType.Contains },
|
||||
{ "Google", PathCheckType.Contains },
|
||||
{ "Program Files", PathCheckType.Contains },
|
||||
{ "Program Files (x86)", PathCheckType.Contains },
|
||||
{ "Drive Root", PathCheckType.DriveRoot }
|
||||
};
|
||||
|
||||
foreach (var check in problemPaths)
|
||||
foreach (var name in problemNames)
|
||||
{
|
||||
switch (check.CheckType)
|
||||
switch (name.Value)
|
||||
{
|
||||
case PathCheckType.EndsWith:
|
||||
if (path.ToLower().EndsWith(check.Target.ToLower()))
|
||||
if (path.ToLower().EndsWith(name.Key.ToLower()))
|
||||
{
|
||||
failedCheck = check;
|
||||
detectedName = name.Key;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case PathCheckType.Contains:
|
||||
if (path.ToLower().Contains(check.Target.ToLower()))
|
||||
if (path.ToLower().Contains(name.Key.ToLower()))
|
||||
{
|
||||
failedCheck = check;
|
||||
detectedName = name.Key;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case PathCheckType.DriveRoot:
|
||||
if (Regex.Match(path.ToLower(), @"^\w:(\\|\/)$").Success)
|
||||
{
|
||||
failedCheck = check;
|
||||
detectedName = name.Key;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -6,8 +6,7 @@ namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class HttpClientProgressExtensions
|
||||
{
|
||||
public static async Task<bool> DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination,
|
||||
IProgress<double> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress<double> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
using (var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
{
|
||||
@ -18,22 +17,18 @@ public static class HttpClientProgressExtensions
|
||||
if (progress is null || !contentLength.HasValue)
|
||||
{
|
||||
await download.CopyToAsync(destination);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Such progress and contentLength much reporting Wow!
|
||||
var progressWrapper = new Progress<long>(totalBytes =>
|
||||
progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
||||
var readBytes = await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
||||
return readBytes == contentLength.Value;
|
||||
var progressWrapper = new Progress<long>(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
|
||||
await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
|
||||
}
|
||||
|
||||
static async Task<long> CopyToAsync(this Stream source, Stream destination, int bufferSize,
|
||||
IProgress<long> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress<long> progress = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (bufferSize < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(bufferSize));
|
||||
@ -49,14 +44,11 @@ public static class HttpClientProgressExtensions
|
||||
var buffer = new byte[bufferSize];
|
||||
long totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
while ((bytesRead =
|
||||
await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||
{
|
||||
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||
totalBytesRead += bytesRead;
|
||||
progress?.Report(totalBytesRead);
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
}
|
||||
}
|
@ -7,8 +7,7 @@ namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class PreCheckHelper
|
||||
{
|
||||
private const string registryInstall =
|
||||
@"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov";
|
||||
private const string registryInstall = @"Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov";
|
||||
|
||||
public static string DetectOriginalGamePath()
|
||||
{
|
||||
@ -20,18 +19,14 @@ public static class PreCheckHelper
|
||||
?.GetValue("InstallLocation");
|
||||
var info = (uninstallStringValue is string key) ? new DirectoryInfo(key) : null;
|
||||
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
return Path.TrimEndingDirectorySeparator(info.FullName);
|
||||
return info?.FullName;
|
||||
}
|
||||
|
||||
public static Result DetectOriginalGameVersion(string gamePath)
|
||||
{
|
||||
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 Result.FromSuccess(version);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -38,7 +38,7 @@ public static class ProcessHelper
|
||||
switch ((PatcherExitCode)process.ExitCode)
|
||||
{
|
||||
case PatcherExitCode.Success:
|
||||
return Result.FromSuccess("Patcher Finished Successfully, extracting SPT");
|
||||
return Result.FromSuccess("Patcher Finished Successfully, extracting Aki");
|
||||
|
||||
case PatcherExitCode.ProgramClosed:
|
||||
return Result.FromError("Patcher was closed before completing!");
|
||||
@ -47,16 +47,16 @@ public static class ProcessHelper
|
||||
return Result.FromError("EscapeFromTarkov.exe is missing from the install Path");
|
||||
|
||||
case PatcherExitCode.NoPatchFolder:
|
||||
return Result.FromError("Patchers Folder called 'SPT_Patches' is missing");
|
||||
return Result.FromError("Patchers Folder called 'Aki_Patches' is missing");
|
||||
|
||||
case PatcherExitCode.MissingFile:
|
||||
return Result.FromError("Vital EFT files were not found. The installer is unable to continue. Please reinstall EFT and try again.");
|
||||
return Result.FromError("EFT files was missing a Vital file to continue");
|
||||
|
||||
case PatcherExitCode.PatchFailed:
|
||||
return Result.FromError("A patch failed to apply");
|
||||
|
||||
default:
|
||||
return Result.FromError("An unknown error occurred in the patcher");
|
||||
return Result.FromError("an unknown error occurred in the patcher");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,11 +39,11 @@ internal static class ServiceHelper
|
||||
{
|
||||
var constructors = typeof(T2).GetConstructors();
|
||||
|
||||
foreach (var constructor in constructors)
|
||||
foreach(var constructor in constructors)
|
||||
{
|
||||
var parmesan = constructor.GetParameters();
|
||||
|
||||
if (parmesan.Length == 0)
|
||||
if(parmesan.Length == 0)
|
||||
{
|
||||
if (TryRegisterInstance<T, T2>()) return;
|
||||
|
||||
@ -52,7 +52,7 @@ internal static class ServiceHelper
|
||||
|
||||
List<object> parameters = new List<object>();
|
||||
|
||||
for (int i = 0; i < parmesan.Length; i++)
|
||||
for(int i = 0; i < parmesan.Length; i++)
|
||||
{
|
||||
var parm = parmesan[i];
|
||||
|
||||
|
@ -5,8 +5,7 @@ namespace SPTInstaller.Helpers;
|
||||
|
||||
public static class ZipHelper
|
||||
{
|
||||
public static Result Decompress(FileInfo archiveFile, DirectoryInfo outputDirectory,
|
||||
IProgress<double> progress = null)
|
||||
public static Result Decompress(FileInfo archiveFile, DirectoryInfo outputDirectory, IProgress<double> progress = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -18,7 +17,10 @@ public static class ZipHelper
|
||||
|
||||
var extractor = new SevenZipExtractor(archiveStream);
|
||||
|
||||
extractor.Extracting += (_, args) => { progress.Report(args.PercentDone); };
|
||||
extractor.Extracting += (_, args) =>
|
||||
{
|
||||
progress.Report(args.PercentDone);
|
||||
};
|
||||
|
||||
extractor.ExtractArchive(outputDirectory.FullName);
|
||||
|
||||
|
@ -24,7 +24,6 @@ public class CopyClientTask : InstallerTaskBase
|
||||
// relative path for exclusions
|
||||
var exclusions = new[] { "\\Logs" };
|
||||
|
||||
return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo,
|
||||
(message, progress) => { SetStatus(null, message, progress, null, true); }, exclusions);
|
||||
return FileHelper.CopyDirectoryWithProgress(originalGameDirInfo, targetInstallDirInfo, (message, progress) => { SetStatus(null, message, progress, null, true); }, exclusions);
|
||||
}
|
||||
}
|
@ -23,13 +23,29 @@ public class DownloadTask : InstallerTaskBase
|
||||
|
||||
private async Task<IResult> BuildMirrorList()
|
||||
{
|
||||
foreach (var mirror in _data.PatchInfo.Mirrors)
|
||||
var progress = new Progress<double>((d) => { SetStatus("Downloading Mirror List", "", (int)Math.Floor(d), ProgressStyle.Shown);});
|
||||
|
||||
var file = await DownloadCacheHelper.DownloadFileAsync("mirrors.json", _data.PatcherMirrorsLink, progress);
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
return Result.FromError("Failed to download mirror list");
|
||||
}
|
||||
|
||||
var mirrorsList = JsonConvert.DeserializeObject<List<DownloadMirror>>(File.ReadAllText(file.FullName));
|
||||
|
||||
if (mirrorsList == null)
|
||||
{
|
||||
return Result.FromError("Failed to deserialize mirrors list");
|
||||
}
|
||||
|
||||
foreach (var mirror in mirrorsList)
|
||||
{
|
||||
_expectedPatcherHash = mirror.Hash;
|
||||
|
||||
switch (mirror.Link)
|
||||
{
|
||||
case { } l when l.StartsWith("https://mega"):
|
||||
case string l when l.StartsWith("https://mega"):
|
||||
_mirrors.Add(new MegaMirrorDownloader(mirror));
|
||||
break;
|
||||
default:
|
||||
@ -45,11 +61,10 @@ public class DownloadTask : InstallerTaskBase
|
||||
{
|
||||
SetStatus("Downloading Patcher", "Verifying cached patcher ...", progressStyle: ProgressStyle.Indeterminate);
|
||||
|
||||
if (DownloadCacheHelper.CheckCacheHash("patcher", _expectedPatcherHash, out var cacheFile))
|
||||
if (DownloadCacheHelper.CheckCache("patcher", _expectedPatcherHash, out var cacheFile))
|
||||
{
|
||||
_data.PatcherZipInfo = cacheFile;
|
||||
Log.Information("Using cached file {fileName} - Hash: {hash}", _data.PatcherZipInfo.Name,
|
||||
_expectedPatcherHash);
|
||||
Log.Information("Using cached file {fileName} - Hash: {hash}", _data.PatcherZipInfo.Name, _expectedPatcherHash);
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
|
||||
@ -68,25 +83,6 @@ public class DownloadTask : InstallerTaskBase
|
||||
return Result.FromError("Failed to download Patcher");
|
||||
}
|
||||
|
||||
private async Task<IResult> DownloadSPTFromMirrors(IProgress<double> progress)
|
||||
{
|
||||
// Note that GetOrDownloadFileAsync handles the cached file hash check, so we don't need to check it first
|
||||
foreach (var mirror in _data.ReleaseInfo.Mirrors)
|
||||
{
|
||||
SetStatus("Downloading SPT", mirror.DownloadUrl, progressStyle: ProgressStyle.Indeterminate);
|
||||
|
||||
_data.SPTZipInfo =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("SPT", mirror.DownloadUrl, progress, mirror.Hash);
|
||||
|
||||
if (_data.SPTZipInfo != null)
|
||||
{
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
return Result.FromError("Failed to download SPT");
|
||||
}
|
||||
|
||||
public override async Task<IResult> TaskOperation()
|
||||
{
|
||||
var progress = new Progress<double>((d) => { SetStatus(null, null, (int)Math.Floor(d)); });
|
||||
@ -110,6 +106,15 @@ public class DownloadTask : InstallerTaskBase
|
||||
}
|
||||
}
|
||||
|
||||
return await DownloadSPTFromMirrors(progress);
|
||||
SetStatus("Downloading SPT-AKI", _data.AkiReleaseDownloadLink, 0);
|
||||
|
||||
_data.AkiZipInfo = await DownloadCacheHelper.GetOrDownloadFileAsync("sptaki", _data.AkiReleaseDownloadLink, progress, _data.AkiReleaseHash);
|
||||
|
||||
if (_data.AkiZipInfo == null)
|
||||
{
|
||||
return Result.FromError("Failed to download spt-aki");
|
||||
}
|
||||
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
}
|
@ -31,14 +31,12 @@ public class InitializationTask : InstallerTaskBase
|
||||
|
||||
if (_data.OriginalGamePath == null)
|
||||
{
|
||||
return Result.FromError(
|
||||
"Unable to find original EFT directory, please make sure EFT is installed. Please also run EFT once");
|
||||
return Result.FromError("Unable to find original EFT directory, please make sure EFT is installed. Please also run EFT once");
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Join(_data.TargetInstallPath, "EscapeFromTarkov.exe")))
|
||||
{
|
||||
return Result.FromError(
|
||||
"Install location is a folder that has existing game files. Please make sure the folder doesn't contain an existing SPT install");
|
||||
return Result.FromError("Installer is located in a folder that has existing game files. Please make sure the installer is in an empty folder as per the guide");
|
||||
}
|
||||
|
||||
return Result.FromSuccess($"Current Game Version: {_data.OriginalGameVersion}");
|
||||
|
@ -1,24 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.Installer_Tasks.PreChecks;
|
||||
|
||||
public class EftInstalledPreCheck : PreCheckBase
|
||||
{
|
||||
private InternalData _internalData;
|
||||
|
||||
public EftInstalledPreCheck(InternalData data) : base("EFT Installed", true)
|
||||
{
|
||||
_internalData = data;
|
||||
}
|
||||
|
||||
public override async Task<PreCheckResult> CheckOperation()
|
||||
{
|
||||
if (_internalData.OriginalGamePath is null || !Directory.Exists(_internalData.OriginalGamePath) || !File.Exists(Path.Join(_internalData.OriginalGamePath, "Escapefromtarkov.exe")))
|
||||
{
|
||||
return PreCheckResult.FromError("Your EFT installation could not be found, try running the Battlestate Games Launcher and ensure EFT is installed on your computer", "Retry", RequestReevaluation);
|
||||
}
|
||||
|
||||
return PreCheckResult.FromSuccess("EFT install folder found");
|
||||
}
|
||||
}
|
@ -17,27 +17,27 @@ public class EftLauncherPreCheck : PreCheckBase
|
||||
|
||||
return eftLauncherProcs.Length == 0
|
||||
? PreCheckResult.FromSuccess("Eft launcher is closed")
|
||||
: PreCheckResult.FromError("Your Battlestate Games Launcher is open. Please close it to continue installing SPT",
|
||||
: PreCheckResult.FromError("Eft launcher is open. Please close it to install SPT",
|
||||
"Kill EFT Launcher Processes",
|
||||
() =>
|
||||
{
|
||||
var bsgLauncherProcs = Process.GetProcessesByName("BsgLauncher");
|
||||
|
||||
foreach (var proc in bsgLauncherProcs)
|
||||
{
|
||||
var bsgLauncherProcs = Process.GetProcessesByName("BsgLauncher");
|
||||
|
||||
foreach (var proc in bsgLauncherProcs)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
proc.Kill();
|
||||
proc.WaitForExit();
|
||||
Log.Information($"Killed Proc: {proc.ProcessName}#{proc.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to kill proc: {proc.ProcessName}#{proc.Id}");
|
||||
}
|
||||
proc.Kill();
|
||||
proc.WaitForExit();
|
||||
Log.Information($"Killed Proc: {proc.ProcessName}#{proc.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to kill proc: {proc.ProcessName}#{proc.Id}");
|
||||
}
|
||||
}
|
||||
|
||||
RequestReevaluation();
|
||||
});
|
||||
RequestReevaluation();
|
||||
});
|
||||
}
|
||||
}
|
@ -27,66 +27,27 @@ public class FreeSpacePreCheck : PreCheckBase
|
||||
var eftSourceDirectoryInfo = new DirectoryInfo(_internalData.OriginalGamePath);
|
||||
var installTargetDirectoryInfo = new DirectoryInfo(_internalData.TargetInstallPath);
|
||||
|
||||
var cacheDirectory = new DirectoryInfo(DownloadCacheHelper.CachePath);
|
||||
|
||||
var eftSourceDirSize = DirectorySizeHelper.GetSizeOfDirectory(eftSourceDirectoryInfo);
|
||||
|
||||
if (eftSourceDirSize == -1)
|
||||
{
|
||||
return PreCheckResult.FromError("An error occurred while getting the EFT source directory size. This is most likely because EFT is not installed");
|
||||
return PreCheckResult.FromError("An error occurred while getting the EFT source directory size");
|
||||
}
|
||||
|
||||
var availableSize = DriveInfo.GetDrives()
|
||||
.FirstOrDefault(d => d.Name.ToLower() == installTargetDirectoryInfo.Root.Name.ToLower())
|
||||
?.AvailableFreeSpace ?? 0;
|
||||
var availableSize = DriveInfo.GetDrives().FirstOrDefault(d => d.Name.ToLower() == installTargetDirectoryInfo.Root.Name.ToLower())?.AvailableFreeSpace ?? 0;
|
||||
|
||||
// add 10Gb overhead to game files for potential patches / release files
|
||||
eftSourceDirSize += 10000000000;
|
||||
|
||||
var availableSpaceMessage = $"Available Space: {DirectorySizeHelper.SizeSuffix(availableSize, 2)}";
|
||||
var requiredSpaceMessage =
|
||||
$"Space Required for EFT Client: {DirectorySizeHelper.SizeSuffix(eftSourceDirSize, 2)} including ~10Gb overhead";
|
||||
|
||||
var cacheDriveMessage = "";
|
||||
var cacheDriveOK = true;
|
||||
|
||||
// if cache directory is on another drive, check that drive for around 5Gb of required space
|
||||
if (cacheDirectory.Root.Name.ToLower() != installTargetDirectoryInfo.Root.Name.ToLower())
|
||||
{
|
||||
cacheDriveOK = false;
|
||||
var availableCacheDriveSize = DriveInfo.GetDrives()
|
||||
.FirstOrDefault(d =>
|
||||
d.Name.ToLower() == cacheDirectory.Root.Name.ToLower())
|
||||
?.AvailableFreeSpace ??
|
||||
0;
|
||||
|
||||
// check if the drive where the cache is has at least 5Gb of free space. We should only need 2-3Gb
|
||||
if (availableCacheDriveSize > 5000000000)
|
||||
{
|
||||
cacheDriveMessage = $"Drive for cache '{cacheDirectory.Root.Name}' has at least 5Gb of space. Available: {DirectorySizeHelper.SizeSuffix(availableCacheDriveSize, 2)}";
|
||||
cacheDriveOK = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
cacheDriveMessage = $"Drive for cache '{cacheDirectory.Root.Name}' does NOT have at least 5Gb of space. Available: {DirectorySizeHelper.SizeSuffix(availableCacheDriveSize, 2)}";
|
||||
}
|
||||
}
|
||||
var requiredSpaceMessage = $"Space Required for EFT Client: {DirectorySizeHelper.SizeSuffix(eftSourceDirSize, 2)} including ~10Gb overhead";
|
||||
|
||||
if (eftSourceDirSize > availableSize)
|
||||
{
|
||||
return PreCheckResult.FromError(
|
||||
$"Not enough free space on {installTargetDirectoryInfo.Root.Name} to install SPT\n\n{availableSpaceMessage}\n{requiredSpaceMessage}\n\n{cacheDriveMessage}");
|
||||
return PreCheckResult.FromError($"Not enough free space on {installTargetDirectoryInfo.Root.Name} to install SPT\n\n{availableSpaceMessage}\n{requiredSpaceMessage}");
|
||||
}
|
||||
|
||||
var okGameSpaceMessage =
|
||||
$"There is enough space available on {installTargetDirectoryInfo.Root.Name} to install SPT.\n\n{availableSpaceMessage}\n{requiredSpaceMessage}\n\n{cacheDriveMessage}";
|
||||
|
||||
if (!cacheDriveOK)
|
||||
{
|
||||
return PreCheckResult.FromError(okGameSpaceMessage);
|
||||
}
|
||||
|
||||
return PreCheckResult.FromSuccess(okGameSpaceMessage);
|
||||
return PreCheckResult.FromSuccess($"There is enough space available on {installTargetDirectoryInfo.Root.Name} to install SPT.\n\n{availableSpaceMessage}\n{requiredSpaceMessage}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -27,24 +27,18 @@ public class Net8PreCheck : PreCheckBase
|
||||
FileName = "cmd.exe",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ArgumentList =
|
||||
{
|
||||
"/C", "start",
|
||||
"https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.2-windows-x64-installer"
|
||||
}
|
||||
ArgumentList = { "/C", "start", "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-8.0.2-windows-x64-installer" }
|
||||
});
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%");
|
||||
var result =
|
||||
ProcessHelper.RunAndReadProcessOutputs($@"{programFiles}\dotnet\dotnet.exe", "--list-runtimes");
|
||||
var result = ProcessHelper.RunAndReadProcessOutputs($@"{programFiles}\dotnet\dotnet.exe", "--list-runtimes");
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return PreCheckResult.FromError(result.Message + "\n\nYou most likely don't have .net 8 installed",
|
||||
failedButtonText, failedButtonAction);
|
||||
return PreCheckResult.FromError(result.Message + "\n\nYou most likely don't have .net 8 installed", failedButtonText, failedButtonAction);
|
||||
}
|
||||
|
||||
output = result.StdOut.Split("\r\n");
|
||||
@ -70,15 +64,12 @@ public class Net8PreCheck : PreCheckBase
|
||||
|
||||
if (foundVersion >= minRequiredVersion)
|
||||
{
|
||||
return PreCheckResult.FromSuccess(
|
||||
$".Net {minRequiredVersion} Desktop Runtime or higher is installed.\n\nInstalled Version: {foundVersion}");
|
||||
return PreCheckResult.FromSuccess($".Net {minRequiredVersion} Desktop Runtime or higher is installed.\n\nInstalled Version: {foundVersion}");
|
||||
}
|
||||
|
||||
highestFoundVersion = foundVersion > highestFoundVersion ? foundVersion : highestFoundVersion;
|
||||
}
|
||||
|
||||
return PreCheckResult.FromError(
|
||||
$".Net Desktop Runtime version {minRequiredVersion} or higher is required.\n\nHighest Version Found: {(highestFoundVersion > new Version("0.0.0") ? highestFoundVersion : "Not Found")}\n\nThis is required to play SPT",
|
||||
failedButtonText, failedButtonAction);
|
||||
return PreCheckResult.FromError($".Net Desktop Runtime version {minRequiredVersion} or higher is required.\n\nHighest Version Found: {(highestFoundVersion > new Version("0.0.0") ? highestFoundVersion : "Not Found")}\n\nThis is required to play SPT", failedButtonText, failedButtonAction);
|
||||
}
|
||||
}
|
@ -7,8 +7,6 @@ using SPTInstaller.Helpers;
|
||||
|
||||
namespace SPTInstaller.Installer_Tasks.PreChecks;
|
||||
|
||||
[Obsolete(
|
||||
"No longer needed, but keeping around for now just in case. Can be removed from code after 7/1/2024 if no issues are found")]
|
||||
public class NetCore6PreCheck : PreCheckBase
|
||||
{
|
||||
public NetCore6PreCheck() : base(".Net Core 6 Desktop Runtime", true)
|
||||
@ -29,24 +27,18 @@ public class NetCore6PreCheck : PreCheckBase
|
||||
FileName = "cmd.exe",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ArgumentList =
|
||||
{
|
||||
"/C", "start",
|
||||
"https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.4-windows-x64-installer"
|
||||
}
|
||||
ArgumentList = { "/C", "start", "https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-6.0.4-windows-x64-installer" }
|
||||
});
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%");
|
||||
var result =
|
||||
ProcessHelper.RunAndReadProcessOutputs($@"{programFiles}\dotnet\dotnet.exe", "--list-runtimes");
|
||||
var result = ProcessHelper.RunAndReadProcessOutputs($@"{programFiles}\dotnet\dotnet.exe", "--list-runtimes");
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return PreCheckResult.FromError(result.Message + "\n\nYou most likely don't have .net 6 installed",
|
||||
failedButtonText, failedButtonAction);
|
||||
return PreCheckResult.FromError(result.Message + "\n\nYou most likely don't have .net 6 installed", failedButtonText, failedButtonAction);
|
||||
}
|
||||
|
||||
output = result.StdOut.Split("\r\n");
|
||||
@ -72,15 +64,12 @@ public class NetCore6PreCheck : PreCheckBase
|
||||
|
||||
if (foundVersion >= minRequiredVersion)
|
||||
{
|
||||
return PreCheckResult.FromSuccess(
|
||||
$".Net Core {minRequiredVersion} Desktop Runtime or higher is installed.\n\nInstalled Version: {foundVersion}");
|
||||
return PreCheckResult.FromSuccess($".Net Core {minRequiredVersion} Desktop Runtime or higher is installed.\n\nInstalled Version: {foundVersion}");
|
||||
}
|
||||
|
||||
highestFoundVersion = foundVersion > highestFoundVersion ? foundVersion : highestFoundVersion;
|
||||
}
|
||||
|
||||
return PreCheckResult.FromError(
|
||||
$".Net Core Desktop Runtime version {minRequiredVersion} or higher is required.\n\nHighest Version Found: {(highestFoundVersion > new Version("0.0.0") ? highestFoundVersion : "Not Found")}\n\nThis is required to play SPT, but you can install it later if and shouldn't affect the SPT install process.",
|
||||
failedButtonText, failedButtonAction);
|
||||
return PreCheckResult.FromError($".Net Core Desktop Runtime version {minRequiredVersion} or higher is required.\n\nHighest Version Found: {(highestFoundVersion > new Version("0.0.0") ? highestFoundVersion : "Not Found")}\n\nThis is required to play SPT, but you can install it later if and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
|
||||
}
|
||||
}
|
@ -29,40 +29,30 @@ public class NetFramework472PreCheck : PreCheckBase
|
||||
FileName = "cmd.exe",
|
||||
UseShellExecute = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ArgumentList =
|
||||
{
|
||||
"/C", "start",
|
||||
"https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer"
|
||||
}
|
||||
ArgumentList = { "/C", "start", "https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net472-developer-pack-offline-installer" }
|
||||
});
|
||||
};
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
return PreCheckResult.FromError(
|
||||
"Could not find .Net Framework on system.\n\nThis is required to play SPT, but you can install it later and shouldn't affect the SPT install process.",
|
||||
failedButtonText, failedButtonAction);
|
||||
return PreCheckResult.FromError("Could not find .Net Framework on system.\n\nThis is required to play SPT, but you can install it later and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
|
||||
}
|
||||
|
||||
var value = key.GetValue("Version");
|
||||
|
||||
if (value == null || value is not string versionString)
|
||||
{
|
||||
return PreCheckResult.FromError(
|
||||
"Something went wrong. This precheck failed for an unknown reason. :(");
|
||||
return PreCheckResult.FromError("Something went wrong. This precheck failed for an unknown reason. :(");
|
||||
}
|
||||
|
||||
var installedVersion = new Version(versionString);
|
||||
|
||||
if (installedVersion < minRequiredVersion)
|
||||
{
|
||||
return PreCheckResult.FromError(
|
||||
$".Net Framework {versionString} is installed, but {minRequiredVersion} or higher is required.\n\nYou can install it later and shouldn't affect the SPT install process.",
|
||||
failedButtonText, failedButtonAction);
|
||||
return PreCheckResult.FromError($".Net Framework {versionString} is installed, but {minRequiredVersion} or higher is required.\n\nYou can install it later and shouldn't affect the SPT install process.", failedButtonText, failedButtonAction);
|
||||
}
|
||||
|
||||
return PreCheckResult.FromSuccess(
|
||||
$".Net Framework {minRequiredVersion} or higher is installed.\n\nInstalled Version: {installedVersion}");
|
||||
return PreCheckResult.FromSuccess($".Net Framework {minRequiredVersion} or higher is installed.\n\nInstalled Version: {installedVersion}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -3,13 +3,10 @@ using SPTInstaller.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPTInstaller.Installer_Tasks.PreChecks;
|
||||
|
||||
public class TestPreCheck : PreCheckBase
|
||||
{
|
||||
private StatusSpinner.SpinnerState _endState;
|
||||
|
||||
public static TestPreCheck FromRandomName(StatusSpinner.SpinnerState EndState) => new TestPreCheck(
|
||||
$"{EndState} #{new Random().Next(0, 9999)}", EndState == StatusSpinner.SpinnerState.Error, EndState);
|
||||
public static TestPreCheck FromRandomName(StatusSpinner.SpinnerState EndState) => new TestPreCheck($"{EndState} #{new Random().Next(0, 9999)}", EndState == StatusSpinner.SpinnerState.Error, EndState);
|
||||
|
||||
public TestPreCheck(string name, bool isRequired, StatusSpinner.SpinnerState endState) : base(name, isRequired)
|
||||
{
|
||||
|
@ -1,10 +1,9 @@
|
||||
using SPTInstaller.Interfaces;
|
||||
using Gitea.Api;
|
||||
using Gitea.Client;
|
||||
using SPTInstaller.Interfaces;
|
||||
using SPTInstaller.Models;
|
||||
using System.Threading.Tasks;
|
||||
using SPTInstaller.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using SPTInstaller.Models.Mirrors;
|
||||
using SPTInstaller.Models.ReleaseInfo;
|
||||
|
||||
namespace SPTInstaller.Installer_Tasks;
|
||||
|
||||
@ -21,80 +20,52 @@ public class ReleaseCheckTask : InstallerTaskBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var repo = new RepositoryApi(Configuration.Default);
|
||||
|
||||
SetStatus("Checking SPT Releases", "", null, ProgressStyle.Indeterminate);
|
||||
|
||||
var progress = new Progress<double>((d) => { SetStatus(null, null, (int)Math.Floor(d)); });
|
||||
var SPTReleaseInfoFile =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("release.json", DownloadCacheHelper.ReleaseMirrorUrl,
|
||||
progress, DownloadCacheHelper.SuggestedTtl);
|
||||
|
||||
if (SPTReleaseInfoFile == null)
|
||||
{
|
||||
return Result.FromError("Failed to download release metadata, try clicking the 'Whats this' button below followed by the 'Clear Metadata cache' button");
|
||||
}
|
||||
|
||||
var SPTReleaseInfo =
|
||||
JsonConvert.DeserializeObject<ReleaseInfo>(File.ReadAllText(SPTReleaseInfoFile.FullName));
|
||||
var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases");
|
||||
|
||||
SetStatus("Checking for Patches", "", null, ProgressStyle.Indeterminate);
|
||||
|
||||
var SPTPatchMirrorsFile =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("mirrors.json", DownloadCacheHelper.PatchMirrorUrl,
|
||||
progress, DownloadCacheHelper.SuggestedTtl);
|
||||
var patchRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Downgrade-Patches");
|
||||
|
||||
if (SPTPatchMirrorsFile == null)
|
||||
var latestAkiRelease = akiRepoReleases.FindAll(x => !x.Prerelease)[0];
|
||||
var latestAkiVersion = latestAkiRelease.Name.Replace('(', ' ').Replace(')', ' ').Split(' ')[3];
|
||||
var comparePatchToAki = patchRepoReleases?.Find(x => x.Name.Contains(_data.OriginalGameVersion) && x.Name.Contains(latestAkiVersion));
|
||||
|
||||
_data.PatcherMirrorsLink = comparePatchToAki?.Assets[0].BrowserDownloadUrl;
|
||||
_data.AkiReleaseDownloadLink = latestAkiRelease.Assets[0].BrowserDownloadUrl;
|
||||
_data.AkiReleaseHash = FileHashHelper.GetGiteaReleaseHash(latestAkiRelease);
|
||||
|
||||
int IntAkiVersion = int.Parse(latestAkiVersion);
|
||||
int IntGameVersion = int.Parse(_data.OriginalGameVersion);
|
||||
bool patchNeedCheck = false;
|
||||
|
||||
if (IntGameVersion > IntAkiVersion)
|
||||
{
|
||||
return Result.FromError("Failed to download patch mirror data, try clicking the 'Whats this' button below followed by the 'Clear Metadata cache' button");
|
||||
patchNeedCheck = true;
|
||||
}
|
||||
|
||||
var patchMirrorInfo =
|
||||
JsonConvert.DeserializeObject<PatchInfo>(File.ReadAllText(SPTPatchMirrorsFile.FullName));
|
||||
|
||||
if (SPTReleaseInfo == null || patchMirrorInfo == null)
|
||||
if (IntGameVersion < IntAkiVersion)
|
||||
{
|
||||
return Result.FromError("An error occurred while deserializing SPT or patch data, try clicking the 'Whats this' button below followed by the 'Clear Metadata cache' button");
|
||||
return Result.FromError("Your client is outdated. Please update EFT");
|
||||
|
||||
}
|
||||
|
||||
_data.ReleaseInfo = SPTReleaseInfo;
|
||||
_data.PatchInfo = patchMirrorInfo;
|
||||
int intSPTVersion = int.Parse(SPTReleaseInfo.ClientVersion);
|
||||
int intGameVersion = int.Parse(_data.OriginalGameVersion);
|
||||
|
||||
// note: it's possible the game version could be lower than the SPT version and still need a patch if the major version numbers change
|
||||
// : it's probably a low chance though
|
||||
bool patchNeedCheck = intGameVersion > intSPTVersion;
|
||||
|
||||
if (intGameVersion < intSPTVersion)
|
||||
{
|
||||
return Result.FromError("Your live EFT is out of date. Please update it using the Battlestate Games Launcher and try runing the SPT Installer again");
|
||||
}
|
||||
|
||||
if (intGameVersion == intSPTVersion)
|
||||
if (IntGameVersion == IntAkiVersion)
|
||||
{
|
||||
patchNeedCheck = false;
|
||||
}
|
||||
|
||||
bool sptClientIsOutdated = intSPTVersion != patchMirrorInfo.TargetClientVersion && patchNeedCheck;
|
||||
bool liveClientIsOutdated = intGameVersion != patchMirrorInfo.SourceClientVersion && patchNeedCheck;
|
||||
|
||||
if (sptClientIsOutdated)
|
||||
if (comparePatchToAki == null && patchNeedCheck)
|
||||
{
|
||||
return Result.FromError(
|
||||
"Could not find a downgrade patcher for the version of EFT you have installed." +
|
||||
"\nThis can happen due to one of the following reasons:" +
|
||||
"\n* Live EFT just updated. The SPT team will create a new patcher within 24 hours, hold tight!" +
|
||||
"\n* Live EFT just updated. You have not installed it on your computer using your Battlestate Games launcher");
|
||||
}
|
||||
|
||||
if (liveClientIsOutdated)
|
||||
{
|
||||
return Result.FromError("Your live EFT is out of date. Please update it using your Battlestate Games Launcher then run the SPT Installer again");
|
||||
return Result.FromError("No patcher available for your version");
|
||||
}
|
||||
|
||||
_data.PatchNeeded = patchNeedCheck;
|
||||
|
||||
string status =
|
||||
$"Current Release: {SPTReleaseInfo.ClientVersion} - {(_data.PatchNeeded ? "Patch Available" : "No Patch Needed")}";
|
||||
string status = $"Current Release: {latestAkiVersion} - {(_data.PatchNeeded ? "Patch Available" : "No Patch Needed")}";
|
||||
|
||||
SetStatus(null, status);
|
||||
|
||||
|
@ -35,7 +35,7 @@ public class SetupClientTask : InstallerTaskBase
|
||||
if (_data.PatchNeeded)
|
||||
{
|
||||
// extract patcher files
|
||||
SetStatus("Extracting Patcher", "", 0);
|
||||
SetStatus("Extrating Patcher", "", 0);
|
||||
|
||||
var extractPatcherResult = ZipHelper.Decompress(_data.PatcherZipInfo, patcherOutputDir, progress);
|
||||
|
||||
@ -49,8 +49,7 @@ public class SetupClientTask : InstallerTaskBase
|
||||
|
||||
var patcherDirInfo = patcherOutputDir.GetDirectories("Patcher*", SearchOption.TopDirectoryOnly).First();
|
||||
|
||||
var copyPatcherResult =
|
||||
FileHelper.CopyDirectoryWithProgress(patcherDirInfo, targetInstallDirInfo, progress);
|
||||
var copyPatcherResult = FileHelper.CopyDirectoryWithProgress(patcherDirInfo, targetInstallDirInfo, progress);
|
||||
|
||||
if (!copyPatcherResult.Succeeded)
|
||||
{
|
||||
@ -71,7 +70,7 @@ public class SetupClientTask : InstallerTaskBase
|
||||
// extract release files
|
||||
SetStatus("Extracting Release", "", 0);
|
||||
|
||||
var extractReleaseResult = ZipHelper.Decompress(_data.SPTZipInfo, targetInstallDirInfo, progress);
|
||||
var extractReleaseResult = ZipHelper.Decompress(_data.AkiZipInfo, targetInstallDirInfo, progress);
|
||||
|
||||
if (!extractReleaseResult.Succeeded)
|
||||
{
|
||||
@ -81,7 +80,7 @@ public class SetupClientTask : InstallerTaskBase
|
||||
// cleanup temp files
|
||||
SetStatus("Cleanup", "almost done :)", null, ProgressStyle.Indeterminate);
|
||||
|
||||
if (_data.PatchNeeded)
|
||||
if(_data.PatchNeeded)
|
||||
{
|
||||
patcherOutputDir.Delete(true);
|
||||
patcherEXE.Delete();
|
||||
|
@ -17,7 +17,7 @@ internal class TestTask : InstallerTaskBase
|
||||
var total = 4;
|
||||
var interval = TimeSpan.FromSeconds(1);
|
||||
|
||||
for (var i = 0; i < total; i++)
|
||||
for(var i = 0; i < total; i++)
|
||||
{
|
||||
var count = i + 1;
|
||||
var progressMessage = $"Running Task: {Name}";
|
||||
|
@ -2,9 +2,8 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPTInstaller.Interfaces;
|
||||
|
||||
public interface IMirrorDownloader
|
||||
{
|
||||
public PatchInfoMirror MirrorInfo { get; }
|
||||
public DownloadMirror MirrorInfo { get; }
|
||||
public Task<FileInfo?> Download(IProgress<double> progress);
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
using Serilog;
|
||||
using SPTInstaller.Helpers;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
class CopyInfo(string sourcePath, string targetPath)
|
||||
{
|
||||
public string FileName => $"{Path.GetFileName(sourcePath)}";
|
||||
public Result Copy()
|
||||
{
|
||||
try
|
||||
{
|
||||
var directory = Path.GetDirectoryName(targetPath);
|
||||
Directory.CreateDirectory(directory);
|
||||
Log.Debug($"COPY\nSource: {FileHelper.GetRedactedPath(sourcePath)}\nTarget: {FileHelper.GetRedactedPath(targetPath)}");
|
||||
File.Copy(sourcePath, targetPath);
|
||||
return Result.FromSuccess();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Result.FromError(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
public class InstallerInfo
|
||||
{
|
||||
public string LatestVersion { get; set; }
|
||||
public List<string> Changes { get; set; }
|
||||
}
|
@ -8,7 +8,6 @@ namespace SPTInstaller.Models;
|
||||
public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
{
|
||||
private string _id;
|
||||
|
||||
public string Id
|
||||
{
|
||||
get => _id;
|
||||
@ -16,7 +15,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
@ -24,7 +22,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private bool _isComleted;
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get => _isComleted;
|
||||
@ -32,7 +29,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private bool _hasErrors;
|
||||
|
||||
public bool HasErrors
|
||||
{
|
||||
get => _hasErrors;
|
||||
@ -40,7 +36,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get => _isRunning;
|
||||
@ -48,7 +43,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private int _progress;
|
||||
|
||||
public int Progress
|
||||
{
|
||||
get => _progress;
|
||||
@ -56,7 +50,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private bool _showProgress;
|
||||
|
||||
public bool ShowProgress
|
||||
{
|
||||
get => _showProgress;
|
||||
@ -64,7 +57,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private bool _indeterminateProgress;
|
||||
|
||||
public bool IndeterminateProgress
|
||||
{
|
||||
get => _indeterminateProgress;
|
||||
@ -72,7 +64,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private string _statusMessage;
|
||||
|
||||
public string StatusMessage
|
||||
{
|
||||
get => _statusMessage;
|
||||
@ -80,7 +71,6 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
}
|
||||
|
||||
private string _statusDetails;
|
||||
|
||||
public string StatusDetails
|
||||
{
|
||||
get => _statusDetails;
|
||||
@ -101,10 +91,9 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
/// <param name="details">The details of the task. Not updated if null</param>
|
||||
/// <param name="progress">Progress of the task. Overrides progressStyle if a non-null value is supplied</param>
|
||||
/// <param name="progressStyle">The style of the progress bar</param>
|
||||
public void SetStatus(string? message, string? details, int? progress = null, ProgressStyle? progressStyle = null,
|
||||
bool noLog = false)
|
||||
public void SetStatus(string? message, string? details, int? progress = null, ProgressStyle? progressStyle = null, bool noLog = false)
|
||||
{
|
||||
if (message != null && message != StatusMessage)
|
||||
if(message != null && message != StatusMessage)
|
||||
{
|
||||
if (!noLog && !string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
@ -114,7 +103,7 @@ public abstract class InstallerTaskBase : ReactiveObject, IProgressableTask
|
||||
StatusMessage = message;
|
||||
}
|
||||
|
||||
if (details != null && details != StatusDetails)
|
||||
if(details != null && details != StatusDetails)
|
||||
{
|
||||
if (!noLog && !string.IsNullOrWhiteSpace(details))
|
||||
{
|
||||
|
@ -1,40 +1,33 @@
|
||||
using ReactiveUI;
|
||||
using Gitea.Api;
|
||||
using Gitea.Client;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using SPTInstaller.Helpers;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
public class InstallerUpdateInfo : ReactiveObject
|
||||
{
|
||||
private Version? _newVersion;
|
||||
|
||||
public Version? NewVersion
|
||||
{
|
||||
get => _newVersion;
|
||||
set => this.RaiseAndSetIfChanged(ref _newVersion, value);
|
||||
}
|
||||
|
||||
private string _changeLog;
|
||||
|
||||
public string ChangeLog
|
||||
{
|
||||
get => _changeLog;
|
||||
set => this.RaiseAndSetIfChanged(ref _changeLog, value);
|
||||
}
|
||||
public string NewInstallerUrl = "";
|
||||
|
||||
private string _updateInfoText = "";
|
||||
|
||||
public string UpdateInfoText
|
||||
{
|
||||
get => _updateInfoText;
|
||||
set => this.RaiseAndSetIfChanged(ref _updateInfoText, value);
|
||||
}
|
||||
|
||||
private bool _updating = false;
|
||||
private bool _show = false;
|
||||
public bool Show
|
||||
{
|
||||
get => _show;
|
||||
set => this.RaiseAndSetIfChanged(ref _show, value);
|
||||
}
|
||||
|
||||
private bool _updating = false;
|
||||
public bool Updating
|
||||
{
|
||||
get => _updating;
|
||||
@ -42,7 +35,6 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private bool _updateAvailable = false;
|
||||
|
||||
public bool UpdateAvailable
|
||||
{
|
||||
get => _updateAvailable;
|
||||
@ -50,7 +42,6 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private bool _checkingForUpdates = false;
|
||||
|
||||
public bool CheckingForUpdates
|
||||
{
|
||||
get => _checkingForUpdates;
|
||||
@ -58,7 +49,6 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private int _downloadProgress;
|
||||
|
||||
public int DownloadProgress
|
||||
{
|
||||
get => _downloadProgress;
|
||||
@ -87,28 +77,23 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
|
||||
var newInstallerPath = await DownloadNewInstaller();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newInstallerPath))
|
||||
if(string.IsNullOrWhiteSpace(newInstallerPath))
|
||||
return;
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
ArgumentList =
|
||||
{
|
||||
"-ExecutionPolicy", "Bypass", "-File", $"{updater.FullName}", $"{newInstallerPath}",
|
||||
$"{Path.Join(Environment.CurrentDirectory, "SPTInstaller.exe")}"
|
||||
}
|
||||
ArgumentList = { "-ExecutionPolicy", "Bypass", "-File", $"{updater.FullName}", $"{newInstallerPath}", $"{Path.Join(Environment.CurrentDirectory, "SPTInstaller.exe")}" }
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<string> DownloadNewInstaller()
|
||||
{
|
||||
UpdateInfoText = $"Downloading installer v{NewVersion}";
|
||||
UpdateInfoText = $"Downloading installer v{_newVersion}";
|
||||
|
||||
var progress = new Progress<double>(x => DownloadProgress = (int)x);
|
||||
|
||||
var file = await DownloadCacheHelper.DownloadFileAsync("SPTInstaller.exe", DownloadCacheHelper.InstallerUrl,
|
||||
progress);
|
||||
var file = await DownloadCacheHelper.DownloadFileAsync("SPTInstller.exe", NewInstallerUrl, progress);
|
||||
|
||||
if (file == null || !file.Exists)
|
||||
{
|
||||
@ -127,6 +112,7 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
}
|
||||
|
||||
UpdateInfoText = infoText;
|
||||
Show = updateAvailable;
|
||||
CheckingForUpdates = false;
|
||||
UpdateAvailable = updateAvailable;
|
||||
}
|
||||
@ -137,45 +123,42 @@ public class InstallerUpdateInfo : ReactiveObject
|
||||
return;
|
||||
|
||||
UpdateInfoText = "Checking for installer updates";
|
||||
Show = true;
|
||||
CheckingForUpdates = true;
|
||||
|
||||
try
|
||||
{
|
||||
var installerInfoFile =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("installer.json", DownloadCacheHelper.InstallerInfoUrl, null
|
||||
, DownloadCacheHelper.SuggestedTtl);
|
||||
var repo = new RepositoryApi(Configuration.Default);
|
||||
|
||||
if (installerInfoFile == null)
|
||||
var releases = await repo.RepoListReleasesAsync("CWX", "SPT-AKI-Installer");
|
||||
|
||||
if (releases == null || releases.Count == 0)
|
||||
{
|
||||
EndCheck("Failed to download installer info", false);
|
||||
EndCheck("No releases available", false);
|
||||
return;
|
||||
}
|
||||
|
||||
var installerInfo =
|
||||
JsonConvert.DeserializeObject<InstallerInfo>(File.ReadAllText(installerInfoFile.FullName));
|
||||
var latest = releases.FindAll(x => !x.Prerelease)[0];
|
||||
|
||||
if (installerInfo == null)
|
||||
if (latest == null)
|
||||
{
|
||||
EndCheck("Failed to parse installer info json", false);
|
||||
EndCheck("could not get latest release", false);
|
||||
return;
|
||||
}
|
||||
|
||||
var latestVersion = new Version(installerInfo.LatestVersion);
|
||||
var latestVersion = new Version(latest.TagName);
|
||||
|
||||
if (latestVersion <= currentVersion)
|
||||
if (latestVersion == null || latestVersion <= currentVersion)
|
||||
{
|
||||
EndCheck("No updates available", false);
|
||||
return;
|
||||
}
|
||||
|
||||
NewVersion = latestVersion;
|
||||
_newVersion = latestVersion;
|
||||
|
||||
foreach (var change in installerInfo.Changes)
|
||||
{
|
||||
ChangeLog += $"◉ {change}\n";
|
||||
}
|
||||
NewInstallerUrl = latest.Assets[0].BrowserDownloadUrl;
|
||||
|
||||
EndCheck($"Update Installer: v{latestVersion}", true);
|
||||
EndCheck($"Update available: v{latestVersion}", true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
using SPTInstaller.Models.Mirrors;
|
||||
using SPTInstaller.Models.ReleaseInfo;
|
||||
using System.Collections.Generic;
|
||||
using SPTInstaller.Models.Mirrors;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
public class InternalData
|
||||
{
|
||||
public bool DebugMode { get; set; } = false;
|
||||
/// <summary>
|
||||
/// The folder to install SPT into
|
||||
/// </summary>
|
||||
@ -27,21 +26,24 @@ public class InternalData
|
||||
public FileInfo PatcherZipInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SPT zip file info
|
||||
/// SPT-AKI zip file info
|
||||
/// </summary>
|
||||
public FileInfo SPTZipInfo { get; set; }
|
||||
public FileInfo AkiZipInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The release information from release.json
|
||||
/// The release download link for SPT-AKI
|
||||
/// </summary>
|
||||
public ReleaseInfo.ReleaseInfo ReleaseInfo { get; set; }
|
||||
public string AkiReleaseDownloadLink { get; set; }
|
||||
|
||||
public PatchInfo PatchInfo { get; set; }
|
||||
/// <summary>
|
||||
/// The release zip hash
|
||||
/// </summary>
|
||||
public string AkiReleaseHash { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// The release download link for the patcher mirror list
|
||||
/// </summary>
|
||||
// public string PatcherMirrorsLink { get; set; }
|
||||
public string PatcherMirrorsLink { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a patch is needed to downgrade the client files
|
||||
|
@ -1,38 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
public enum KnownFolder
|
||||
{
|
||||
Contacts,
|
||||
Downloads,
|
||||
Favorites,
|
||||
Links,
|
||||
SavedGames,
|
||||
SavedSearches
|
||||
}
|
||||
|
||||
public static class KnownFolders
|
||||
{
|
||||
private static readonly Dictionary<KnownFolder, Guid> _guids = new()
|
||||
{
|
||||
[KnownFolder.Contacts] = new("56784854-C6CB-462B-8169-88E350ACB882"),
|
||||
[KnownFolder.Downloads] = new("374DE290-123F-4565-9164-39C4925E467B"),
|
||||
[KnownFolder.Favorites] = new("1777F761-68AD-4D8A-87BD-30B759FA33DD"),
|
||||
[KnownFolder.Links] = new("BFB9D5E0-C6A9-404C-B2B2-AE6DB6AF4968"),
|
||||
[KnownFolder.SavedGames] = new("4C5C32FF-BB9D-43B0-B5B4-2D72E54EAAA4"),
|
||||
[KnownFolder.SavedSearches] = new("7D1D3A04-DEBB-4115-95CF-2F29DA2920DA")
|
||||
};
|
||||
|
||||
public static string GetPath(KnownFolder knownFolder)
|
||||
{
|
||||
return SHGetKnownFolderPath(_guids[knownFolder], 0);
|
||||
}
|
||||
|
||||
[DllImport("shell32",
|
||||
CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
|
||||
private static extern string SHGetKnownFolderPath(
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags,
|
||||
nint hToken = 0);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace SPTInstaller.Models.Mirrors;
|
||||
|
||||
public class PatchInfoMirror
|
||||
public class DownloadMirror
|
||||
{
|
||||
public string Link { get; set; }
|
||||
public string Hash { get; set; }
|
@ -2,10 +2,9 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPTInstaller.Models.Mirrors.Downloaders;
|
||||
|
||||
public class HttpMirrorDownloader : MirrorDownloaderBase
|
||||
{
|
||||
public HttpMirrorDownloader(PatchInfoMirror mirror) : base(mirror)
|
||||
public HttpMirrorDownloader(DownloadMirror mirror) : base(mirror)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,9 @@ using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace SPTInstaller.Models.Mirrors.Downloaders;
|
||||
|
||||
public class MegaMirrorDownloader : MirrorDownloaderBase
|
||||
{
|
||||
public MegaMirrorDownloader(PatchInfoMirror mirrorInfo) : base(mirrorInfo)
|
||||
public MegaMirrorDownloader(DownloadMirror mirrorInfo) : base(mirrorInfo)
|
||||
{
|
||||
}
|
||||
|
||||
@ -39,7 +38,7 @@ public class MegaMirrorDownloader : MirrorDownloaderBase
|
||||
|
||||
return FileHashHelper.CheckHash(file, MirrorInfo.Hash) ? file : null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception thrown while downloading from mega");
|
||||
//most likely a 509 (Bandwidth limit exceeded) due to mega's user quotas.
|
||||
|
@ -2,13 +2,11 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SPTInstaller.Models.Mirrors.Downloaders;
|
||||
|
||||
public abstract class MirrorDownloaderBase : IMirrorDownloader
|
||||
{
|
||||
public PatchInfoMirror MirrorInfo { get; private set; }
|
||||
public DownloadMirror MirrorInfo { get; private set; }
|
||||
public abstract Task<FileInfo?> Download(IProgress<double> progress);
|
||||
|
||||
public MirrorDownloaderBase(PatchInfoMirror mirrorInfo)
|
||||
public MirrorDownloaderBase(DownloadMirror mirrorInfo)
|
||||
{
|
||||
MirrorInfo = mirrorInfo;
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SPTInstaller.Models.Mirrors;
|
||||
|
||||
public class PatchInfo
|
||||
{
|
||||
public int SourceClientVersion { get; set; }
|
||||
public int TargetClientVersion { get; set; }
|
||||
public List<PatchInfoMirror> Mirrors { get; set; }
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
public enum PathCheckType
|
||||
{
|
||||
EndsWith = 0,
|
||||
Contains = 1,
|
||||
DriveRoot = 2
|
||||
}
|
||||
|
||||
public enum PathCheckAction
|
||||
{
|
||||
Warn = 0,
|
||||
Deny = 1,
|
||||
}
|
||||
|
||||
public class PathCheck
|
||||
{
|
||||
public string Target { get; private set; }
|
||||
public PathCheckType CheckType { get; private set; }
|
||||
public PathCheckAction CheckAction { get; private set; }
|
||||
|
||||
public PathCheck()
|
||||
{
|
||||
}
|
||||
|
||||
public PathCheck(string target, PathCheckType checkType, PathCheckAction checkAction)
|
||||
{
|
||||
Target = target;
|
||||
CheckType = checkType;
|
||||
CheckAction = checkAction;
|
||||
}
|
||||
}
|
@ -8,9 +8,8 @@ namespace SPTInstaller.Models;
|
||||
|
||||
public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
{
|
||||
public event EventHandler ReeevaluationRequested = delegate { };
|
||||
public event EventHandler ReeevaluationRequested = delegate { };
|
||||
private string _id;
|
||||
|
||||
public string Id
|
||||
{
|
||||
get => _id;
|
||||
@ -18,7 +17,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
@ -26,7 +24,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
@ -34,7 +31,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private bool _required;
|
||||
|
||||
public bool IsRequired
|
||||
{
|
||||
get => _required;
|
||||
@ -42,7 +38,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private StatusSpinner.SpinnerState _state;
|
||||
|
||||
public StatusSpinner.SpinnerState State
|
||||
{
|
||||
get => _state;
|
||||
@ -50,7 +45,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private string _preCheckDetails;
|
||||
|
||||
public string PreCheckDetails
|
||||
{
|
||||
get => _preCheckDetails;
|
||||
@ -58,7 +52,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private bool _actionButtonIsVisible;
|
||||
|
||||
public bool ActionButtonIsVisible
|
||||
{
|
||||
get => _actionButtonIsVisible;
|
||||
@ -66,7 +59,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private string _actionButtonText;
|
||||
|
||||
public string ActionButtonText
|
||||
{
|
||||
get => _actionButtonText;
|
||||
@ -74,7 +66,6 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
}
|
||||
|
||||
private ICommand _actionButtonCommand;
|
||||
|
||||
public ICommand ActionButtonCommand
|
||||
{
|
||||
get => _actionButtonCommand;
|
||||
@ -96,9 +87,9 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
private StatusSpinner.SpinnerState ProcessResult(PreCheckResult result) =>
|
||||
(result.Succeeded, IsRequired) switch
|
||||
{
|
||||
(true, _) => StatusSpinner.SpinnerState.OK,
|
||||
(true, _) => StatusSpinner.SpinnerState.OK,
|
||||
(false, false) => StatusSpinner.SpinnerState.Warning,
|
||||
(_, _) => StatusSpinner.SpinnerState.Error
|
||||
(_, _) => StatusSpinner.SpinnerState.Error
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -117,9 +108,7 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
|
||||
PreCheckDetails = !string.IsNullOrWhiteSpace(result.Message)
|
||||
? result.Message
|
||||
: (result.Succeeded
|
||||
? "Pre-Check succeeded, but no details were provided"
|
||||
: "Pre-Check failed, but no details were provided");
|
||||
: (result.Succeeded ? "Pre-Check succeeded, but no details were provided" : "Pre-Check failed, but no details were provided");
|
||||
|
||||
ActionButtonText = result.ActionButtonText;
|
||||
ActionButtonCommand = result.ButtonPressedCommand;
|
||||
@ -127,9 +116,7 @@ public abstract class PreCheckBase : ReactiveObject, IPreCheck
|
||||
|
||||
State = ProcessResult(result);
|
||||
|
||||
return State == StatusSpinner.SpinnerState.OK
|
||||
? Result.FromSuccess()
|
||||
: Result.FromError($"PreCheck Failed: {Name}");
|
||||
return State == StatusSpinner.SpinnerState.OK ? Result.FromSuccess() : Result.FromError($"PreCheck Failed: {Name}");
|
||||
}
|
||||
|
||||
public abstract Task<PreCheckResult> CheckOperation();
|
||||
|
@ -15,7 +15,6 @@ public class PreCheckDetailInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private string _name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
@ -23,7 +22,6 @@ public class PreCheckDetailInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private string _details;
|
||||
|
||||
public string Details
|
||||
{
|
||||
get => _details;
|
||||
@ -31,7 +29,6 @@ public class PreCheckDetailInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private string _actionButtonText;
|
||||
|
||||
public string ActionButtonText
|
||||
{
|
||||
get => _actionButtonText;
|
||||
@ -39,7 +36,6 @@ public class PreCheckDetailInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private ICommand _actionButtonCommand;
|
||||
|
||||
public ICommand ActionButtonCommand
|
||||
{
|
||||
get => _actionButtonCommand;
|
||||
@ -47,7 +43,6 @@ public class PreCheckDetailInfo : ReactiveObject
|
||||
}
|
||||
|
||||
private bool _showActionButton;
|
||||
|
||||
public bool ShowActionButton
|
||||
{
|
||||
get => _showActionButton;
|
||||
|
@ -3,7 +3,6 @@ using SPTInstaller.Interfaces;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace SPTInstaller.Models;
|
||||
|
||||
public class PreCheckResult : IResult
|
||||
{
|
||||
public bool Succeeded { get; private set; }
|
||||
@ -32,13 +31,7 @@ public class PreCheckResult : IResult
|
||||
|
||||
public static PreCheckResult FromSuccess(string message = "") => new PreCheckResult(message, true, "", null);
|
||||
|
||||
public static PreCheckResult FromError(string message, string actionButtonText = "",
|
||||
Action? actionButtonPressedAction = null) =>
|
||||
new PreCheckResult(message, false, actionButtonText, actionButtonPressedAction);
|
||||
public static PreCheckResult FromError(string message, string actionButtonText = "", Action? actionButtonPressedAction = null) => new PreCheckResult(message, false, actionButtonText, actionButtonPressedAction);
|
||||
|
||||
public static PreCheckResult
|
||||
FromException(Exception ex, string actionButtonText = "", Action? actionButtonPressedAction = null) =>
|
||||
new PreCheckResult(
|
||||
$"An exception was thrown during this precheck\n\nException:\n{ex.Message}\n\nStacktrace:\n{ex.StackTrace}",
|
||||
false, actionButtonText, actionButtonPressedAction);
|
||||
public static PreCheckResult FromException(Exception ex, string actionButtonText = "", Action? actionButtonPressedAction = null) => new PreCheckResult($"An exception was thrown during this precheck\n\nException:\n{ex.Message}\n\nStacktrace:\n{ex.StackTrace}", false, actionButtonText, actionButtonPressedAction);
|
||||
}
|
@ -5,8 +5,7 @@ public class ReadProcessResult : Result
|
||||
public string StdOut { get; }
|
||||
public string StdErr { get; }
|
||||
|
||||
protected ReadProcessResult(string message, bool succeeded, string stdOut = "", string stdErr = "") : base(message,
|
||||
succeeded)
|
||||
protected ReadProcessResult(string message, bool succeeded, string stdOut = "", string stdErr = "") : base(message, succeeded)
|
||||
{
|
||||
StdOut = stdOut;
|
||||
StdErr = stdErr;
|
||||
|
@ -1,12 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace SPTInstaller.Models.ReleaseInfo;
|
||||
|
||||
public class ReleaseInfo
|
||||
{
|
||||
[JsonProperty("AkiVersion")] // TODO: Change this and what gets uploaded to SPTVersion
|
||||
public string SPTVersion { get; set; }
|
||||
public string ClientVersion { get; set; }
|
||||
public List<ReleaseInfoMirror> Mirrors { get; set; }
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace SPTInstaller.Models.ReleaseInfo;
|
||||
|
||||
public class ReleaseInfoMirror
|
||||
{
|
||||
public string DownloadUrl { get; set; }
|
||||
public string Hash { get; set; }
|
||||
}
|
@ -44,8 +44,8 @@ internal class Program
|
||||
ServiceHelper.Register<InternalData>();
|
||||
|
||||
#if !TEST
|
||||
ServiceHelper.Register<PreCheckBase, EftInstalledPreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, NetFramework472PreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, NetCore6PreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, Net8PreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, FreeSpacePreCheck>();
|
||||
ServiceHelper.Register<PreCheckBase, EftLauncherPreCheck>();
|
||||
|
BIN
SPTInstaller/Resources/Gitea.dll
Normal file
BIN
SPTInstaller/Resources/Gitea.dll
Normal file
Binary file not shown.
@ -1,23 +0,0 @@
|
||||
param (
|
||||
[string]$installPath
|
||||
)
|
||||
|
||||
$desktop = Join-Path $env:USERPROFILE "Desktop"
|
||||
|
||||
$launcherExe = gci $installPath | where {$_.Name -like "*.Launcher.exe"} | select -ExpandProperty FullName
|
||||
$serverExe = gci $installPath | where {$_.Name -like "*.Server.exe"} | select -ExpandProperty FullName
|
||||
|
||||
$launcherShortcut = Join-Path $desktop "SPT.Launcher.lnk"
|
||||
$serverShortcut = Join-Path $desktop "SPT.Server.lnk"
|
||||
|
||||
$WshShell = New-Object -comObject WScript.Shell
|
||||
|
||||
$launcher = $WshShell.CreateShortcut($launcherShortcut)
|
||||
$launcher.TargetPath = $launcherExe
|
||||
$launcher.WorkingDirectory = $installPath
|
||||
$launcher.Save()
|
||||
|
||||
$server = $WshShell.CreateShortcut($serverShortcut)
|
||||
$server.TargetPath = $serverExe
|
||||
$server.WorkingDirectory = $installPath
|
||||
$server.Save()
|
@ -5,69 +5,22 @@
|
||||
|
||||
Clear-Host
|
||||
|
||||
Write-Host "Stopping installer ... " -ForegroundColor cyan -NoNewLine
|
||||
Write-Host "Stopping installer ..."
|
||||
|
||||
$installer = Stop-Process -Name "SPTInstaller" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($installer -ne $null)
|
||||
{
|
||||
Write-Warning "Something went wrong, couldn't stop installer process'"
|
||||
if ($installer -ne $null) {
|
||||
Write-Host "Something went wrong, couldn't stop installer process'"
|
||||
return;
|
||||
}
|
||||
|
||||
Write-Host "OK" -ForegroundColor green
|
||||
Write-Host "Copying new installer ..."
|
||||
|
||||
if (-not(Test-Path $source) -and -not(Test-Path $destination)) {
|
||||
Write-Warning "Can't find a required file"
|
||||
Write-host ""
|
||||
Write-Host "Press [enter] to close ..."
|
||||
Read-Host
|
||||
exit
|
||||
}
|
||||
Import-Module BitsTransfer
|
||||
|
||||
Write-Host "Copying new installer ... " -ForegroundColor cyan
|
||||
Start-BitsTransfer -Source $source -Destination $destination
|
||||
|
||||
$maxAttempts = 10
|
||||
$copied = $false
|
||||
|
||||
while (-not $copied) {
|
||||
|
||||
$maxAttempts--
|
||||
|
||||
Write-Host " > Please wait ... " -NoNewLine
|
||||
|
||||
if ($maxAttempts -le 0) {
|
||||
Write-Host "Couldn't copy new installer :( Please re-download the installer"
|
||||
Write-Host ""
|
||||
Write-Host "Press [enter] to close ..."
|
||||
Read-Host
|
||||
exit
|
||||
}
|
||||
|
||||
try {
|
||||
Remove-Item $destination -ErrorAction SilentlyContinue
|
||||
Copy-Item $source $destination -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch {
|
||||
Write-Host "file locked, retrying ..." -ForegroundColor yellow
|
||||
sleep(2)
|
||||
continue
|
||||
}
|
||||
|
||||
if (Test-Path $destination) {
|
||||
$sLength = (Get-Item $source).Length
|
||||
$dLength = (Get-Item $destination).Length
|
||||
|
||||
if ($sLength -eq $dLength) {
|
||||
$copied = $true
|
||||
Write-Host "OK" -ForegroundColor green
|
||||
break
|
||||
}
|
||||
|
||||
Write-Host "sizes differ, retrying ..." -ForegroundColor yellow
|
||||
sleep(2)
|
||||
}
|
||||
}
|
||||
Remove-Module BitsTransfer
|
||||
|
||||
# remove the new installer from the cache folder after it is copied
|
||||
Remove-Item -Path $source
|
||||
|
@ -1,5 +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"/>
|
||||
<!-- Can be removed if CompiledBinding and no reflection are used -->
|
||||
<assembly fullname="SPTInstaller" preserve="All" />
|
||||
<assembly fullname="Avalonia.Themes.Fluent" preserve="All" />
|
||||
</linker>
|
||||
|
@ -1,53 +1,56 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||
<Nullable>enable</Nullable>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<!-- TODO: To change -->
|
||||
<PackageIcon>icon.ico</PackageIcon>
|
||||
<ApplicationIcon>Assets\spt_installer.ico</ApplicationIcon>
|
||||
<Configurations>Debug;Release;TEST</Configurations>
|
||||
<AssemblyVersion>2.90</AssemblyVersion>
|
||||
<FileVersion>2.90</FileVersion>
|
||||
<Company>SPT</Company>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.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>
|
||||
<AssemblyVersion>2.55</AssemblyVersion>
|
||||
<FileVersion>2.55</FileVersion>
|
||||
<Company>SPT-AKI</Company>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**"/>
|
||||
<None Remove=".gitignore"/>
|
||||
<None Remove="Assets\spt_installer.ico"/>
|
||||
<None Remove="Resources\update.ps1"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
<None Remove=".gitignore" />
|
||||
<None Remove="Assets\icon.ico" />
|
||||
<None Remove="Resources\update.ps1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\update.ps1"/>
|
||||
<None Remove="Resources\7z.dll"/>
|
||||
<EmbeddedResource Include="Resources\7z.dll"/>
|
||||
<None Remove="Resources\add_shortcuts.ps1" />
|
||||
<EmbeddedResource Include="Resources\add_shortcuts.ps1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\update.ps1" />
|
||||
<None Remove="Resources\7z.dll" />
|
||||
<EmbeddedResource Include="Resources\7z.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<TrimmerRootDescriptor Include="Roots.xml"/>
|
||||
</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="11.0.5"/>
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.5"/>
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.5"/>
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5"/>
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5"/>
|
||||
<PackageReference Include="DialogHost.Avalonia" Version="0.7.7"/>
|
||||
<PackageReference Include="FubarCoder.RestSharp.Portable.HttpClient" Version="4.0.8"/>
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.4"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0"/>
|
||||
<PackageReference Include="SerilogTraceListener" Version="3.2.0"/>
|
||||
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23"/>
|
||||
<PackageReference Include="System.Reactive" Version="6.0.0"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
<PackageReference Include="Avalonia" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.5" />
|
||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
|
||||
<PackageReference Include="DialogHost.Avalonia" Version="0.7.7" />
|
||||
<PackageReference Include="FubarCoder.RestSharp.Portable.HttpClient" Version="4.0.8" />
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.4" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SerilogTraceListener" Version="3.2.0" />
|
||||
<PackageReference Include="Squid-Box.SevenZipSharp" Version="1.6.1.23" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Gitea">
|
||||
<HintPath>Z:\dev_stuff\EftPatchHelper\EftPatchHelper\EftPatchHelper\Resources\Gitea.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,157 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Platform.Storage;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
public class InstallPathSelectionViewModel : ViewModelBase
|
||||
{
|
||||
private InternalData _data;
|
||||
|
||||
private string _selectedPath;
|
||||
|
||||
public string SelectedPath
|
||||
{
|
||||
get => _selectedPath;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedPath, value);
|
||||
}
|
||||
|
||||
private bool _validPath;
|
||||
public bool ValidPath
|
||||
{
|
||||
get => _validPath;
|
||||
set => this.RaiseAndSetIfChanged(ref _validPath, value);
|
||||
}
|
||||
|
||||
private string _errorMessage;
|
||||
|
||||
public string ErrorMessage
|
||||
{
|
||||
get => _errorMessage;
|
||||
set => this.RaiseAndSetIfChanged(ref _errorMessage, value);
|
||||
}
|
||||
|
||||
public InstallPathSelectionViewModel(IScreen host, string installPath) : base(host)
|
||||
{
|
||||
_data = ServiceHelper.Get<InternalData?>() ?? throw new Exception("Failed to get internal data");
|
||||
SelectedPath = Environment.CurrentDirectory;
|
||||
ValidPath = false;
|
||||
|
||||
if (!string.IsNullOrEmpty(installPath))
|
||||
{
|
||||
SelectedPath = installPath;
|
||||
ValidatePath();
|
||||
|
||||
if (ValidPath)
|
||||
{
|
||||
Log.Information("Install Path was provided by parameter and seems valid");
|
||||
Task.Run(NextCommand);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AdjustInstallPath();
|
||||
}
|
||||
|
||||
public async Task SelectFolderCommand()
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (desktop.MainWindow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var startingFolderPath = Directory.Exists(SelectedPath) ? SelectedPath : Environment.CurrentDirectory;
|
||||
|
||||
var suggestedFolder = await desktop.MainWindow.StorageProvider.TryGetFolderFromPathAsync(startingFolderPath);
|
||||
|
||||
var selections = await desktop.MainWindow.StorageProvider.OpenFolderPickerAsync(
|
||||
new FolderPickerOpenOptions()
|
||||
{
|
||||
AllowMultiple = false,
|
||||
SuggestedStartLocation = suggestedFolder,
|
||||
Title = "Select a folder to install SPT into"
|
||||
});
|
||||
|
||||
SelectedPath = selections.First().Path.LocalPath;
|
||||
}
|
||||
}
|
||||
|
||||
public void ValidatePath()
|
||||
{
|
||||
if (String.IsNullOrEmpty(SelectedPath))
|
||||
{
|
||||
ErrorMessage = "Please provide an install path";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var match = Regex.Match(SelectedPath[2..], @"[\/:*?""<>|]");
|
||||
if (match.Success)
|
||||
{
|
||||
ErrorMessage = "Path cannot contain these characters: / : * ? \" < > |";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FileHelper.CheckPathForProblemLocations(SelectedPath, out var failedCheck))
|
||||
{
|
||||
if (failedCheck.CheckType == PathCheckType.EndsWith)
|
||||
{
|
||||
ErrorMessage = $"You can install in {failedCheck.Target}, but only in a subdirectory. Example: ..\\{failedCheck.Target}\\SPT";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (failedCheck.CheckAction == PathCheckAction.Deny)
|
||||
{
|
||||
ErrorMessage = $"Sorry, you cannot install in {failedCheck.Target}";
|
||||
ValidPath = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ValidPath = true;
|
||||
}
|
||||
|
||||
private void AdjustInstallPath()
|
||||
{
|
||||
if (FileHelper.CheckPathForProblemLocations(SelectedPath, out var failedCheck))
|
||||
{
|
||||
switch (failedCheck.CheckType)
|
||||
{
|
||||
case PathCheckType.EndsWith:
|
||||
SelectedPath = Path.Join(Environment.CurrentDirectory, "SPT");
|
||||
break;
|
||||
|
||||
case PathCheckType.Contains:
|
||||
case PathCheckType.DriveRoot:
|
||||
SelectedPath = Path.Join(Directory.GetDirectoryRoot(Environment.CurrentDirectory), "SPT");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NextCommand()
|
||||
{
|
||||
if (FileHelper.CheckPathForProblemLocations(SelectedPath, out var failedCheck) && failedCheck.CheckAction == PathCheckAction.Deny)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_data.TargetInstallPath = SelectedPath;
|
||||
|
||||
NavigateTo(new PreChecksViewModel(HostScreen));
|
||||
}
|
||||
}
|
@ -11,15 +11,13 @@ 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(ServiceHelper.GetAll<InstallerTaskBase>());
|
||||
public ObservableCollection<InstallerTaskBase> MyTasks { get; set; } = new(ServiceHelper.GetAll<InstallerTaskBase>());
|
||||
|
||||
public InstallViewModel(IScreen host) : base(host)
|
||||
{
|
||||
|
@ -1,42 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
public class InstallerUpdateViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
public InstallerUpdateInfo UpdateInfo { get; set; } = new();
|
||||
private InternalData _data;
|
||||
|
||||
private string _installPath;
|
||||
public InstallerUpdateViewModel(IScreen Host, string installPath) : base(Host)
|
||||
{
|
||||
_installPath = installPath;
|
||||
|
||||
_data = ServiceHelper.Get<InternalData>() ?? throw new Exception("Failed to get internal data");
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await UpdateInfo.CheckForUpdates(Assembly.GetExecutingAssembly().GetName().Version);
|
||||
|
||||
if (!UpdateInfo.UpdateAvailable)
|
||||
{
|
||||
NavigateTo(new OverviewViewModel(HostScreen, _installPath));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void NotNowCommand()
|
||||
{
|
||||
NavigateTo(new OverviewViewModel(HostScreen, _installPath));
|
||||
}
|
||||
|
||||
public async Task UpdateInstallCommand()
|
||||
{
|
||||
await UpdateInfo.UpdateInstaller();
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
using Avalonia;
|
||||
using Gitea.Client;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
@ -14,34 +13,31 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
|
||||
public ViewModelActivator Activator { get; } = new();
|
||||
|
||||
private string _title;
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => this.RaiseAndSetIfChanged(ref _title, value);
|
||||
}
|
||||
|
||||
public MainWindowViewModel(string installPath)
|
||||
public MainWindowViewModel(bool debugging)
|
||||
{
|
||||
var data = ServiceHelper.Get<InternalData>() ?? throw new Exception("failed to get interanl data");
|
||||
Configuration.Default.BasePath = "https://dev.sp-tarkov.com/api/v1";
|
||||
|
||||
Title =
|
||||
$"{(data.DebugMode ? "-debug-" : "")} SPT Installer {"v" + Assembly.GetExecutingAssembly().GetName()?.Version?.ToString() ?? "--unknown version--"}";
|
||||
Title = $"{(debugging ? "-debug-" : "")} SPT Installer {"v" + Assembly.GetExecutingAssembly().GetName()?.Version?.ToString() ?? "--unknown version--"}";
|
||||
|
||||
Log.Information($"========= {Title} Started =========");
|
||||
Log.Information(Environment.OSVersion.VersionString);
|
||||
|
||||
var uiCulture = CultureInfo.InstalledUICulture;
|
||||
var uiCulture= CultureInfo.InstalledUICulture;
|
||||
|
||||
Log.Information("System Language: {iso} - {name}", uiCulture.TwoLetterISOLanguageName, uiCulture.DisplayName);
|
||||
|
||||
Router.Navigate.Execute(new InstallerUpdateViewModel(this, installPath));
|
||||
Router.Navigate.Execute(new PreChecksViewModel(this, debugging));
|
||||
}
|
||||
|
||||
public void CloseCommand()
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is
|
||||
Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
desktopApp.MainWindow.Close();
|
||||
}
|
||||
@ -49,8 +45,7 @@ public class MainWindowViewModel : ReactiveObject, IActivatableViewModel, IScree
|
||||
|
||||
public void MinimizeCommand()
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is
|
||||
Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
desktopApp.MainWindow.WindowState = Avalonia.Controls.WindowState.Minimized;
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Avalonia;
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using SPTInstaller.CustomControls;
|
||||
@ -8,17 +6,12 @@ using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Platform.Storage;
|
||||
using SPTInstaller.Models;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
public class MessageViewModel : ViewModelBase
|
||||
{
|
||||
private bool _HasErrors;
|
||||
|
||||
public bool HasErrors
|
||||
{
|
||||
get => _HasErrors;
|
||||
@ -26,7 +19,6 @@ public class MessageViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private string _Message;
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => _Message;
|
||||
@ -34,99 +26,20 @@ public class MessageViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private bool _showCloseButton;
|
||||
|
||||
public bool ShowCloseButton
|
||||
{
|
||||
get => _showCloseButton;
|
||||
set => this.RaiseAndSetIfChanged(ref _showCloseButton, value);
|
||||
}
|
||||
|
||||
private bool _showOptions;
|
||||
|
||||
public bool ShowOptions
|
||||
{
|
||||
get => _showOptions;
|
||||
set => this.RaiseAndSetIfChanged(ref _showOptions, value);
|
||||
}
|
||||
|
||||
private string _cacheInfoText;
|
||||
|
||||
public string CacheInfoText
|
||||
{
|
||||
get => _cacheInfoText;
|
||||
set => this.RaiseAndSetIfChanged(ref _cacheInfoText, value);
|
||||
}
|
||||
|
||||
private string _clipCommandText;
|
||||
|
||||
public string ClipCommandText
|
||||
{
|
||||
get => _clipCommandText;
|
||||
set => this.RaiseAndSetIfChanged(ref _clipCommandText, value);
|
||||
}
|
||||
|
||||
private bool _addShortcuts;
|
||||
public bool AddShortcuts
|
||||
{
|
||||
get => _addShortcuts;
|
||||
set => this.RaiseAndSetIfChanged(ref _addShortcuts, value);
|
||||
}
|
||||
|
||||
private bool _openInstallFolder = true;
|
||||
public bool OpenInstallFolder
|
||||
{
|
||||
get => _openInstallFolder;
|
||||
set => this.RaiseAndSetIfChanged(ref _openInstallFolder, value);
|
||||
}
|
||||
|
||||
public ICommand CopyLogFileToClipboard => ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var data = ServiceHelper.Get<InternalData>();
|
||||
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (desktop.MainWindow?.Clipboard == null)
|
||||
{
|
||||
ClipCommandText = "Could not get clipboard :(";
|
||||
return;
|
||||
}
|
||||
|
||||
var dataObject = new DataObject();
|
||||
|
||||
var filesToCopy = new List<IStorageFile>();
|
||||
|
||||
var logFile = await desktop.MainWindow.StorageProvider.TryGetFileFromPathAsync(data.DebugMode ? App.LogDebugPath : App.LogPath);
|
||||
var patcherLogFile = await desktop.MainWindow.StorageProvider.TryGetFileFromPathAsync(Path.Join(data.TargetInstallPath, "patcher.log"));
|
||||
|
||||
if (logFile == null)
|
||||
{
|
||||
ClipCommandText = "Could not get log file :(";
|
||||
return;
|
||||
}
|
||||
|
||||
filesToCopy.Add(logFile);
|
||||
|
||||
if (patcherLogFile != null)
|
||||
{
|
||||
filesToCopy.Add(patcherLogFile);
|
||||
}
|
||||
|
||||
dataObject.Set(DataFormats.Files, filesToCopy.ToArray());
|
||||
|
||||
await desktop.MainWindow.Clipboard.SetDataObjectAsync(dataObject);
|
||||
ClipCommandText = "Copied!";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ClipCommandText = ex.Message;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private StatusSpinner.SpinnerState _cacheCheckState;
|
||||
|
||||
public StatusSpinner.SpinnerState CacheCheckState
|
||||
{
|
||||
get => _cacheCheckState;
|
||||
@ -135,7 +48,7 @@ public class MessageViewModel : ViewModelBase
|
||||
|
||||
public ICommand CloseCommand { get; set; } = ReactiveCommand.Create(() =>
|
||||
{
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
if (Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
desktopApp.MainWindow.Close();
|
||||
}
|
||||
@ -145,75 +58,6 @@ public class MessageViewModel : ViewModelBase
|
||||
{
|
||||
ShowCloseButton = showCloseButton;
|
||||
Message = result.Message;
|
||||
ClipCommandText = "Copy installer log to clipboard";
|
||||
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopApp)
|
||||
{
|
||||
var data = ServiceHelper.Get<InternalData?>();
|
||||
|
||||
desktopApp.MainWindow.Closing += (_, _) =>
|
||||
{
|
||||
if (ShowOptions)
|
||||
{
|
||||
if (OpenInstallFolder)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = data.TargetInstallPath
|
||||
});
|
||||
}
|
||||
|
||||
if (AddShortcuts)
|
||||
{
|
||||
var shortcuts = new FileInfo(Path.Join(DownloadCacheHelper.CachePath, "add_shortcuts.ps1"));
|
||||
|
||||
if (!FileHelper.StreamAssemblyResourceOut("add_shortcuts.ps1", shortcuts.FullName))
|
||||
{
|
||||
Log.Fatal("Failed to prepare shortcuts file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(shortcuts.FullName))
|
||||
{
|
||||
Log.Fatal("Shortcuts file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Information("Running add shortcuts script ...");
|
||||
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
CreateNoWindow = true,
|
||||
ArgumentList =
|
||||
{
|
||||
"-ExecutionPolicy", "Bypass", "-File", $"{shortcuts.FullName}", $"{data.TargetInstallPath}"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (data.TargetInstallPath == Environment.CurrentDirectory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
File.Copy(App.LogPath, Path.Join(data.TargetInstallPath, "spt-installer.log"), true);
|
||||
|
||||
if (data.DebugMode)
|
||||
{
|
||||
File.Copy(App.LogDebugPath, Path.Join(data.TargetInstallPath, "spt-installer-debug.log"), true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to copy installer log to install path");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
@ -227,7 +71,6 @@ public class MessageViewModel : ViewModelBase
|
||||
if (result.Succeeded)
|
||||
{
|
||||
Log.Information(Message);
|
||||
ShowOptions = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
public class OverviewViewModel : ViewModelBase
|
||||
{
|
||||
private string _providedPath;
|
||||
public OverviewViewModel(IScreen Host, string providedPath) : base(Host)
|
||||
{
|
||||
_providedPath = providedPath;
|
||||
|
||||
if (!string.IsNullOrEmpty(_providedPath))
|
||||
{
|
||||
Task.Run(NextCommand);
|
||||
}
|
||||
}
|
||||
|
||||
public void NextCommand()
|
||||
{
|
||||
NavigateTo(new InstallPathSelectionViewModel(HostScreen, _providedPath));
|
||||
}
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Threading;
|
||||
using DialogHostAvalonia;
|
||||
using Newtonsoft.Json;
|
||||
using Gitea.Api;
|
||||
using Gitea.Client;
|
||||
using ReactiveUI;
|
||||
using Serilog;
|
||||
using SPTInstaller.Controllers;
|
||||
@ -12,7 +14,6 @@ using SPTInstaller.CustomControls;
|
||||
using SPTInstaller.CustomControls.Dialogs;
|
||||
using SPTInstaller.Helpers;
|
||||
using SPTInstaller.Models;
|
||||
using SPTInstaller.Models.ReleaseInfo;
|
||||
|
||||
namespace SPTInstaller.ViewModels;
|
||||
|
||||
@ -31,8 +32,14 @@ public class PreChecksViewModel : ViewModelBase
|
||||
public ICommand SelectPreCheckCommand { get; set; }
|
||||
public ICommand StartInstallCommand { get; set; }
|
||||
|
||||
public ICommand UpdateInstallerCommand { get; set; }
|
||||
|
||||
public ICommand DismissUpdateCommand { get; set; }
|
||||
|
||||
public ICommand LaunchWithDebug { get; set; }
|
||||
|
||||
public InstallerUpdateInfo UpdateInfo { get; set; } = new();
|
||||
|
||||
private bool _debugging;
|
||||
|
||||
public bool Debugging
|
||||
@ -42,7 +49,6 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private string _installPath;
|
||||
|
||||
public string InstallPath
|
||||
{
|
||||
get => _installPath;
|
||||
@ -58,7 +64,6 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private bool _allowInstall;
|
||||
|
||||
public bool AllowInstall
|
||||
{
|
||||
get => _allowInstall;
|
||||
@ -66,7 +71,6 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private bool _allowDetailsButton = false;
|
||||
|
||||
public bool AllowDetailsButton
|
||||
{
|
||||
get => _allowDetailsButton;
|
||||
@ -74,7 +78,6 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private string _cacheInfoText;
|
||||
|
||||
public string CacheInfoText
|
||||
{
|
||||
get => _cacheInfoText;
|
||||
@ -82,7 +85,6 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private StatusSpinner.SpinnerState _cacheCheckState;
|
||||
|
||||
public StatusSpinner.SpinnerState CacheCheckState
|
||||
{
|
||||
get => _cacheCheckState;
|
||||
@ -90,7 +92,6 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
private StatusSpinner.SpinnerState _installButtonCheckState;
|
||||
|
||||
public StatusSpinner.SpinnerState InstallButtonCheckState
|
||||
{
|
||||
get => _installButtonCheckState;
|
||||
@ -109,13 +110,12 @@ public class PreChecksViewModel : ViewModelBase
|
||||
});
|
||||
}
|
||||
|
||||
public PreChecksViewModel(IScreen host) : base(host)
|
||||
public PreChecksViewModel(IScreen host, bool debugging) : base(host)
|
||||
{
|
||||
Debugging = debugging;
|
||||
var data = ServiceHelper.Get<InternalData?>();
|
||||
var installer = ServiceHelper.Get<InstallController?>();
|
||||
|
||||
Debugging = data.DebugMode;
|
||||
|
||||
installer.RecheckRequested += ReCheckRequested;
|
||||
|
||||
InstallButtonText = "Please wait ...";
|
||||
@ -123,13 +123,13 @@ public class PreChecksViewModel : ViewModelBase
|
||||
|
||||
if (data == null || installer == null)
|
||||
{
|
||||
NavigateTo(new MessageViewModel(HostScreen,
|
||||
Result.FromError("Failed to get required service for prechecks")));
|
||||
NavigateTo(new MessageViewModel(HostScreen, Result.FromError("Failed to get required service for prechecks")));
|
||||
return;
|
||||
}
|
||||
|
||||
data.OriginalGamePath = PreCheckHelper.DetectOriginalGamePath();
|
||||
|
||||
data.TargetInstallPath = Environment.CurrentDirectory;
|
||||
InstallPath = data.TargetInstallPath;
|
||||
|
||||
Log.Information($"Install Path: {FileHelper.GetRedactedPath(InstallPath)}");
|
||||
@ -137,8 +137,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
#if !TEST
|
||||
if (data.OriginalGamePath == null)
|
||||
{
|
||||
NavigateTo(new MessageViewModel(HostScreen,
|
||||
Result.FromError("Could not find where you installed EFT.\n\nDo you own and have the game installed?")));
|
||||
NavigateTo(new MessageViewModel(HostScreen, Result.FromError("Could not find EFT install.\n\nDo you own and have the game installed?")));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@ -147,7 +146,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
|
||||
var logFiles = Directory.GetFiles(InstallPath, "spt-installer_*.log");
|
||||
var logFiles = Directory.GetFiles(InstallPath, "spt-aki-installer_*.log");
|
||||
|
||||
// remove log file from original game path if they exist
|
||||
foreach (var file in logFiles)
|
||||
@ -156,56 +155,28 @@ public class PreChecksViewModel : ViewModelBase
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
NavigateTo(new MessageViewModel(HostScreen,
|
||||
Result.FromError(
|
||||
"You have chosen to install in the same folder as EFT. Please choose a another folder. Refer to the install guide on where best to place the installer before running it."),
|
||||
noLog: true));
|
||||
NavigateTo(new MessageViewModel(HostScreen, Result.FromError("Installer is located in EFT's original directory. Please move the installer to a seperate folder as per the guide"), noLog: true));
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
if (FileHelper.CheckPathForProblemLocations(InstallPath, out var failedCheck))
|
||||
if (FileHelper.CheckPathForProblemLocations(InstallPath, out var detectedName))
|
||||
{
|
||||
switch (failedCheck.CheckAction)
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
case PathCheckAction.Warn:
|
||||
Log.Warning("Problem folder detected, confirming install path ...");
|
||||
var confirmation = await DialogHost.Show(new ConfirmationDialog($"We suspect you may be installing into a problematic folder: {detectedName}.\nYou might want to consider installing somewhere else to avoid issues.\n\nAre you sure you want to install to this path?\n{InstallPath}"));
|
||||
|
||||
if (confirmation == null || !bool.TryParse(confirmation.ToString(), out var confirm) || !confirm)
|
||||
{
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
Log.Warning("Problem path detected, confirming install path ...");
|
||||
var confirmation = await DialogHost.Show(new ConfirmationDialog(
|
||||
$"It appears you are installing into a folder known to cause problems: {failedCheck.Target}." +
|
||||
$"\nPlease consider installing SPT somewhere else to avoid issues later on." +
|
||||
$"\n\nAre you sure you want to install to this path?\n{InstallPath}"));
|
||||
|
||||
if (confirmation == null || !bool.TryParse(confirmation.ToString(), out var confirm) ||
|
||||
!confirm)
|
||||
{
|
||||
Log.Information("User declined install path");
|
||||
NavigateBack();
|
||||
}
|
||||
});
|
||||
|
||||
break;
|
||||
Log.Information("User declined install path, exiting");
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
case PathCheckAction.Deny:
|
||||
{
|
||||
Log.Error("Problem path detected, install denied");
|
||||
NavigateTo(new MessageViewModel(HostScreen,
|
||||
Result.FromError(
|
||||
$"We suspect you may be installing into a problematic folder: {failedCheck.Target}.\nWe won't be letting you install here. How did you do this?")));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
});
|
||||
|
||||
Log.Information("User accepted install path");
|
||||
}
|
||||
@ -215,7 +186,14 @@ public class PreChecksViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
App.ReLaunch(true, InstallPath);
|
||||
var installerPath = Path.Join(_installPath, "SPTInstaller.exe");
|
||||
Process.Start(new ProcessStartInfo()
|
||||
{
|
||||
FileName = installerPath,
|
||||
Arguments = "debug"
|
||||
});
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -223,7 +201,7 @@ public class PreChecksViewModel : ViewModelBase
|
||||
}
|
||||
});
|
||||
|
||||
SelectPreCheckCommand = ReactiveCommand.Create(async (PreCheckBase check) =>
|
||||
SelectPreCheckCommand = ReactiveCommand.Create(async(PreCheckBase check) =>
|
||||
{
|
||||
foreach (var precheck in PreChecks)
|
||||
{
|
||||
@ -242,42 +220,55 @@ public class PreChecksViewModel : ViewModelBase
|
||||
|
||||
StartInstallCommand = ReactiveCommand.Create(async () =>
|
||||
{
|
||||
UpdateInfo.Show = false;
|
||||
NavigateTo(new InstallViewModel(HostScreen));
|
||||
});
|
||||
|
||||
UpdateInstallerCommand = ReactiveCommand.Create(async () =>
|
||||
{
|
||||
AllowDetailsButton = false;
|
||||
AllowInstall = false;
|
||||
await UpdateInfo.UpdateInstaller();
|
||||
});
|
||||
|
||||
DismissUpdateCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
UpdateInfo.Show = false;
|
||||
});
|
||||
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
// run prechecks
|
||||
var result = await installer.RunPreChecks();
|
||||
|
||||
// check for updates
|
||||
await UpdateInfo.CheckForUpdates(Assembly.GetExecutingAssembly().GetName()?.Version);
|
||||
|
||||
// get latest spt version
|
||||
InstallButtonText = "Getting latest release ...";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.Running;
|
||||
|
||||
var progress = new Progress<double>((d) => { });
|
||||
var repo = new RepositoryApi(Configuration.Default);
|
||||
var akiRepoReleases = await repo.RepoListReleasesAsync("SPT-AKI", "Stable-releases");
|
||||
|
||||
var SPTReleaseInfoFile =
|
||||
await DownloadCacheHelper.GetOrDownloadFileAsync("release.json", DownloadCacheHelper.ReleaseMirrorUrl,
|
||||
progress, DownloadCacheHelper.SuggestedTtl);
|
||||
|
||||
if (SPTReleaseInfoFile == null)
|
||||
if (akiRepoReleases == null || akiRepoReleases.Count == 0)
|
||||
{
|
||||
InstallButtonText = "Could not get SPT release metadata";
|
||||
InstallButtonText = "Could not get SPT releases from repo";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
var SPTReleaseInfo =
|
||||
JsonConvert.DeserializeObject<ReleaseInfo>(File.ReadAllText(SPTReleaseInfoFile.FullName));
|
||||
var latestAkiRelease = akiRepoReleases.FindAll(x => !x.Prerelease)[0];
|
||||
|
||||
if (SPTReleaseInfo == null)
|
||||
if (latestAkiRelease == null)
|
||||
{
|
||||
InstallButtonText = "Could not parse latest SPT release";
|
||||
InstallButtonText = "Could not find the latest SPT release";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
InstallButtonText = $"Start Install: SPT v{SPTReleaseInfo.SPTVersion}";
|
||||
InstallButtonText = $"Start Install: SPT v{latestAkiRelease.TagName}";
|
||||
InstallButtonCheckState = StatusSpinner.SpinnerState.OK;
|
||||
|
||||
AllowDetailsButton = true;
|
||||
|
@ -45,12 +45,10 @@ public class ViewModelBase : ReactiveObject, IActivatableViewModel, IRoutableVie
|
||||
/// <param name="ViewModel"></param>
|
||||
public void NavigateTo(ViewModelBase ViewModel)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() => { HostScreen.Router.Navigate.Execute(ViewModel); });
|
||||
}
|
||||
|
||||
public void NavigateBack()
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() => { HostScreen.Router.NavigateBack.Execute(); });
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
HostScreen.Router.Navigate.Execute(ViewModel);
|
||||
});
|
||||
}
|
||||
|
||||
public ViewModelBase(IScreen Host)
|
||||
|
@ -1,65 +0,0 @@
|
||||
<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.InstallPathSelectionView">
|
||||
|
||||
<Grid RowDefinitions="10,*,Auto,*,10" ColumnDefinitions="10,*,Auto,10">
|
||||
|
||||
<!-- Path Controls Grid -->
|
||||
<Grid Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto"
|
||||
VerticalAlignment="Center"
|
||||
>
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="Install Folder Path" FontSize="20"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="0"
|
||||
TextChanged="TextBox_OnTextChanged"
|
||||
Watermark="Where we dropping?"
|
||||
FontSize="16"
|
||||
Text="{Binding SelectedPath}"
|
||||
Classes.hasErrors="{Binding !ValidPath}"
|
||||
>
|
||||
<TextBox.Styles>
|
||||
<Style Selector="TextBox.hasErrors">
|
||||
<Setter Property="Foreground" Value="Red"/>
|
||||
</Style>
|
||||
</TextBox.Styles>
|
||||
</TextBox>
|
||||
<Button Grid.Row="1" Grid.Column="1"
|
||||
CornerRadius="20"
|
||||
Margin="10 0 0 0"
|
||||
Command="{Binding SelectFolderCommand}"
|
||||
>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Path Data="{StaticResource OpenFolder}" Fill="{Binding $parent[Button].Foreground}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Label Content="Select Folder"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- Validation error text -->
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding ErrorMessage}"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="16" Foreground="red" FontWeight="SemiBold"
|
||||
IsVisible="{Binding !ValidPath}"
|
||||
/>
|
||||
|
||||
<!-- Next button -->
|
||||
<Button Grid.Row="3" Grid.Column="2"
|
||||
MinWidth="100"
|
||||
MinHeight="30"
|
||||
FontSize="16"
|
||||
CornerRadius="20"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Classes="yellow"
|
||||
Content="Next"
|
||||
Command="{Binding NextCommand}"
|
||||
IsEnabled="{Binding ValidPath}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,18 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.ReactiveUI;
|
||||
using SPTInstaller.ViewModels;
|
||||
|
||||
namespace SPTInstaller.Views;
|
||||
|
||||
public partial class InstallPathSelectionView : ReactiveUserControl<InstallPathSelectionViewModel>
|
||||
{
|
||||
public InstallPathSelectionView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void TextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
ViewModel?.ValidatePath();
|
||||
}
|
||||
}
|
@ -2,22 +2,24 @@
|
||||
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: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 SPT_Background_Dark}"
|
||||
PendingColor="Gray"
|
||||
RunningColor="DodgerBlue"
|
||||
CompletedColor="{StaticResource SPT_Brush_Yellow}" />
|
||||
<cc:TaskDetails Grid.Column="1"
|
||||
Message="{Binding CurrentTask.StatusMessage}"
|
||||
Details="{Binding CurrentTask.StatusDetails}"
|
||||
Progress="{Binding CurrentTask.Progress}"
|
||||
IndeterminateProgress="{Binding CurrentTask.IndeterminateProgress}"
|
||||
ShowProgress="{Binding CurrentTask.ShowProgress}" />
|
||||
</Grid>
|
||||
<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}"
|
||||
IndeterminateProgress="{Binding CurrentTask.IndeterminateProgress}"
|
||||
ShowProgress="{Binding CurrentTask.ShowProgress}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,31 +0,0 @@
|
||||
<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.InstallerUpdateView">
|
||||
<Grid RowDefinitions="10,Auto,*,10" ColumnDefinitions="10,*,10">
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" IsVisible="{Binding UpdateInfo.UpdateAvailable}">
|
||||
<Label Content="{Binding UpdateInfo.NewVersion, StringFormat='{}Installer Change Log for {0}'}" FontSize="18" FontWeight="SemiBold"
|
||||
/>
|
||||
<Separator Margin="0 10" Padding="0" Background="{StaticResource SPT_Yellow}"/>
|
||||
<ScrollViewer MaxHeight="250" Background="#323232">
|
||||
<TextBlock Text="{Binding UpdateInfo.ChangeLog}"
|
||||
TextWrapping="Wrap" MinHeight="100"
|
||||
Margin="10"
|
||||
/>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
<cc:UpdateButton Grid.Row="2" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
IsIndeterminate="{Binding UpdateInfo.CheckingForUpdates}"
|
||||
InfoText="{Binding UpdateInfo.UpdateInfoText}"
|
||||
Updating="{Binding UpdateInfo.Updating}"
|
||||
DismissCommand="{Binding NotNowCommand}"
|
||||
UpdateCommand="{Binding UpdateInstallCommand}"
|
||||
DownloadProgress="{Binding UpdateInfo.DownloadProgress}"
|
||||
UpdateAvailable="{Binding UpdateInfo.UpdateAvailable}"
|
||||
CheckingForUpdate="{Binding UpdateInfo.CheckingForUpdates}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,12 +0,0 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using SPTInstaller.ViewModels;
|
||||
|
||||
namespace SPTInstaller.Views;
|
||||
|
||||
public partial class InstallerUpdateView : ReactiveUserControl<InstallerUpdateViewModel>
|
||||
{
|
||||
public InstallerUpdateView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
@ -1,39 +1,41 @@
|
||||
<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: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"
|
||||
xmlns:cc="using:SPTInstaller.CustomControls"
|
||||
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.MainWindow"
|
||||
Icon="/Assets/spt_installer.ico"
|
||||
Icon="/Assets/icon.ico"
|
||||
Title="SPT Installer"
|
||||
Height="450" Width="750"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
ExtendClientAreaChromeHints="NoChrome"
|
||||
ExtendClientAreaTitleBarHeightHint="-1"
|
||||
Background="{StaticResource SPT_Background_Light}"
|
||||
MinWidth="800" MinHeight="400">
|
||||
Background="{StaticResource AKI_Background_Light}"
|
||||
MinWidth="800" MinHeight="400"
|
||||
>
|
||||
|
||||
<Window.Styles>
|
||||
<StyleInclude Source="/Assets/Styles.axaml" />
|
||||
</Window.Styles>
|
||||
<Window.Styles>
|
||||
<StyleInclude Source="/Assets/Styles.axaml"/>
|
||||
</Window.Styles>
|
||||
|
||||
|
||||
<Design.DataContext>
|
||||
<vm:MainWindowViewModel />
|
||||
</Design.DataContext>
|
||||
<Design.DataContext>
|
||||
<vm:MainWindowViewModel/>
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid RowDefinitions="AUTO,*">
|
||||
<cc:TitleBar Title="{Binding Title}"
|
||||
XButtonCommand="{Binding CloseCommand}"
|
||||
MinButtonCommand="{Binding MinimizeCommand}" />
|
||||
<Grid RowDefinitions="AUTO,*">
|
||||
<cc:TitleBar Title="{Binding Title}"
|
||||
XButtonCommand="{Binding CloseCommand}"
|
||||
MinButtonCommand="{Binding MinimizeCommand}"
|
||||
/>
|
||||
|
||||
<dialogHost:DialogHost Grid.Row="1" Background="{StaticResource SPT_Background_Light}">
|
||||
<rxui:RoutedViewHost Router="{Binding Router}" />
|
||||
<dialogHost:DialogHost Grid.Row="1" Background="{StaticResource AKI_Background_Light}">
|
||||
<rxui:RoutedViewHost Router="{Binding Router}"/>
|
||||
</dialogHost:DialogHost>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
@ -4,49 +4,43 @@
|
||||
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.MessageView">
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Grid.error">
|
||||
<Setter Property="Background" Value="#330000" />
|
||||
</Style>
|
||||
<Style Selector="Label.error">
|
||||
<Setter Property="Foreground" Value="Crimson" />
|
||||
</Style>
|
||||
x:Class="SPTInstaller.Views.MessageView"
|
||||
>
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Grid.error">
|
||||
<Setter Property="Background" Value="#330000"/>
|
||||
</Style>
|
||||
<Style Selector="Label.error">
|
||||
<Setter Property="Foreground" Value="Crimson"/>
|
||||
</Style>
|
||||
|
||||
</UserControl.Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid ColumnDefinitions="*,AUTO,*" RowDefinitions="*,AUTO,20,AUTO,20,Auto,Auto,*"
|
||||
Classes.error="{Binding HasErrors}">
|
||||
<Grid ColumnDefinitions="*,AUTO,*" RowDefinitions="*,AUTO,20,AUTO,*"
|
||||
Classes.error="{Binding HasErrors}">
|
||||
|
||||
<Label Grid.Column="1" Grid.Row="1"
|
||||
Classes.error="{Binding HasErrors}">
|
||||
<TextBlock Text="{Binding Message}" FontSize="18"
|
||||
TextWrapping="Wrap"
|
||||
MaxWidth="500"
|
||||
HorizontalAlignment="Center" />
|
||||
</Label>
|
||||
<Label Grid.Column="1" Grid.Row="1"
|
||||
Classes.error="{Binding HasErrors}">
|
||||
<TextBlock Text="{Binding Message}" FontSize="18"
|
||||
TextWrapping="Wrap"
|
||||
MaxWidth="500"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Button Grid.Column="1" Grid.Row="3"
|
||||
Content="Close" Command="{Binding CloseCommand}"
|
||||
FontSize="15" FontWeight="SemiBold"
|
||||
Classes.yellow="{Binding !HasErrors}"
|
||||
IsVisible="{Binding ShowCloseButton}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
Padding="20 10" />
|
||||
<Button Grid.Column="1" Grid.Row="3"
|
||||
Content="Close" Command="{Binding CloseCommand}"
|
||||
FontSize="15" FontWeight="SemiBold"
|
||||
Classes.yellow="{Binding !HasErrors}"
|
||||
IsVisible="{Binding ShowCloseButton}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
|
||||
Padding="20 10"
|
||||
/>
|
||||
|
||||
<StackPanel Grid.Row="5" Grid.Column="1" Orientation="Horizontal" Spacing="10">
|
||||
<CheckBox IsChecked="{Binding OpenInstallFolder}" Content="Open Install Folder" IsVisible="{Binding ShowOptions}"/>
|
||||
<CheckBox IsChecked="{Binding AddShortcuts}" Content="Add Desktop Shortcuts" IsVisible="{Binding ShowOptions}"/>
|
||||
</StackPanel>
|
||||
|
||||
<cc:CacheInfo Grid.Row="7" Grid.ColumnSpan="3" Padding="10" Margin="10 0 0 0"
|
||||
<cc:CacheInfo Grid.Row="4" Grid.ColumnSpan="3" Padding="10" Margin="10 0 0 0"
|
||||
VerticalAlignment="Bottom"
|
||||
InfoText="{Binding CacheInfoText}" State="{Binding CacheCheckState}"
|
||||
/>
|
||||
|
||||
<Button Grid.Row="7" Grid.Column="2" Classes="link" Content="{Binding ClipCommandText}"
|
||||
Command="{Binding CopyLogFileToClipboard}" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,55 +0,0 @@
|
||||
<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.OverviewView">
|
||||
<Grid RowDefinitions="10,Auto,Auto,Auto,Auto,*,10" ColumnDefinitions="10,*,Auto,10">
|
||||
|
||||
<!-- Overview text -->
|
||||
<Label Grid.Row="1" Grid.Column="1" Content="This installer will:" FontSize="20" Margin="0 5"
|
||||
Foreground="{StaticResource SPT_Yellow}"
|
||||
/>
|
||||
|
||||
<!-- Overview info -->
|
||||
<StackPanel Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2">
|
||||
<Label>◉ Check dependencies are installed, such as .NET</Label>
|
||||
<Label>◉ Automatically locate and copy your EFT client files to the path you supply on the next page</Label>
|
||||
<Label>◉ Downgrade your client files to the version SPT uses, if needed</Label>
|
||||
<Label>◉ Download and extract the SPT release files</Label>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Notes text -->
|
||||
<Label Grid.Row="3" Grid.Column="1" Content="Additional Notes:" FontSize="20" Margin="0 5"
|
||||
Foreground="{StaticResource SPT_Yellow}"
|
||||
/>
|
||||
|
||||
<!-- Notes info -->
|
||||
<StackPanel Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2">
|
||||
<Label>◉ You do not need to install SPT in the same drive as EFT</Label>
|
||||
<Label Margin="0" Padding="0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label VerticalAlignment="Center">◉ This tool does</Label>
|
||||
<TextBlock TextDecorations="Underline" FontWeight="SemiBold" Foreground="Crimson" VerticalAlignment="Center">NOT</TextBlock>
|
||||
<Label VerticalAlignment="Center">update an existing SPT install</Label>
|
||||
</StackPanel>
|
||||
</Label>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Next button -->
|
||||
<Button Grid.Row="5" Grid.Column="2"
|
||||
MinWidth="100"
|
||||
MinHeight="30"
|
||||
FontSize="16"
|
||||
CornerRadius="20"
|
||||
FontWeight="SemiBold"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Classes="yellow"
|
||||
Content="Next"
|
||||
Command="{Binding NextCommand}"
|
||||
IsEnabled="{Binding ValidPath}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,12 +0,0 @@
|
||||
using Avalonia.ReactiveUI;
|
||||
using SPTInstaller.ViewModels;
|
||||
|
||||
namespace SPTInstaller.Views;
|
||||
|
||||
public partial class OverviewView : ReactiveUserControl<OverviewViewModel>
|
||||
{
|
||||
public OverviewView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
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:cc="using:SPTInstaller.CustomControls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="SPTInstaller.Views.PreChecksView">
|
||||
|
||||
@ -16,33 +16,24 @@
|
||||
<Grid RowDefinitions="10, Auto, *, Auto, 10" ColumnDefinitions="10, 2*, Auto,*, 10">
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center"
|
||||
Content="Details" FontSize="20" Margin="0 2 0 0"/>
|
||||
Content="Details" FontSize="20"
|
||||
/>
|
||||
|
||||
<!-- selected precheck details grid -->
|
||||
<cc:PreCheckDetails Grid.Row="2" Grid.Column="1"
|
||||
PreChecks="{Binding PreChecks}"
|
||||
HasSelection="{Binding HasPreCheckSelected}" />
|
||||
HasSelection="{Binding HasPreCheckSelected}"
|
||||
/>
|
||||
|
||||
<!-- info card vertical separator -->
|
||||
<Rectangle Grid.Row="1" Grid.RowSpan="2" Grid.Column="2" VerticalAlignment="Stretch"
|
||||
Fill="black" Width="1" Margin="10 0" />
|
||||
Fill="black" Width="1" Margin="10 0"
|
||||
/>
|
||||
|
||||
<!-- precheck list -->
|
||||
<Grid Grid.Row="1" Grid.Column="3">
|
||||
<Label HorizontalAlignment="Center"
|
||||
Margin="0 2 0 0"
|
||||
<Label Grid.Row="1" Grid.Column="3" HorizontalAlignment="Center"
|
||||
Content="Pre-Checks" FontSize="20"
|
||||
/>
|
||||
<Button Padding="10" x:Name="debugBtn"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Top"
|
||||
Classes="icon"
|
||||
Command="{Binding LaunchWithDebug}"
|
||||
IsVisible="{Binding !Debugging}"
|
||||
>
|
||||
<Path Data="{StaticResource Bug}" Fill="{Binding ElementName=debugBtn, Path=Foreground}"
|
||||
/>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding PreChecks}" Grid.Row="2" Grid.Column="3">
|
||||
<ItemsControl.ItemsPanel>
|
||||
@ -52,12 +43,13 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<cc:PreCheckItem PreCheckName="{Binding Name}"
|
||||
IsRequired="{Binding IsRequired}"
|
||||
IsSelected="{Binding IsSelected}"
|
||||
State="{Binding State}"
|
||||
SelectCommand="{Binding $parent[ItemsControl].DataContext.SelectPreCheckCommand}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<cc:PreCheckItem PreCheckName="{Binding Name}"
|
||||
IsRequired="{Binding IsRequired}"
|
||||
IsSelected="{Binding IsSelected}"
|
||||
State="{Binding State}"
|
||||
SelectCommand="{Binding $parent[ItemsControl].DataContext.SelectPreCheckCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
@ -65,18 +57,20 @@
|
||||
<!-- cache info -->
|
||||
<cc:CacheInfo Grid.Row="2" Grid.Column="3" Padding="10"
|
||||
VerticalAlignment="Bottom" HorizontalAlignment="Left"
|
||||
InfoText="{Binding CacheInfoText}" State="{Binding CacheCheckState}" />
|
||||
InfoText="{Binding CacheInfoText}" State="{Binding CacheCheckState}"
|
||||
/>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Install path info -->
|
||||
<StackPanel Grid.Row="3" Grid.Column="1">
|
||||
<StackPanel Grid.Row="3" Grid.Column="1" >
|
||||
<Label Content="Install Path" FontSize="20" />
|
||||
|
||||
<TextBlock TextWrapping="Wrap" Margin="3 0"
|
||||
Text="{Binding InstallPath}"
|
||||
Foreground="DodgerBlue" FontWeight="SemiBold" />
|
||||
Foreground="DodgerBlue" FontWeight="SemiBold"
|
||||
/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Start install button -->
|
||||
@ -86,12 +80,26 @@
|
||||
FontSize="15" FontWeight="SemiBold"
|
||||
Classes="yellow"
|
||||
Command="{Binding StartInstallCommand}"
|
||||
CornerRadius="15">
|
||||
CornerRadius="15"
|
||||
>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding InstallButtonText}" VerticalAlignment="Center" Foreground="Black" />
|
||||
<cc:StatusSpinner State="{Binding InstallButtonCheckState}" Margin="2"
|
||||
IsVisible="{Binding !AllowInstall}" />
|
||||
<TextBlock Text="{Binding InstallButtonText}" VerticalAlignment="Center" Foreground="Black"/>
|
||||
<cc:StatusSpinner State="{Binding InstallButtonCheckState}" Margin="2" IsVisible="{Binding !AllowInstall}"/>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Update installer button -->
|
||||
<cc:UpdateButton Grid.Column="2" Grid.Row="3"
|
||||
IsVisible="{Binding UpdateInfo.Show}"
|
||||
IsEnabled="{Binding UpdateInfo.Show}"
|
||||
IsIndeterminate="{Binding UpdateInfo.CheckingForUpdates}"
|
||||
InfoText="{Binding UpdateInfo.UpdateInfoText}"
|
||||
Updating="{Binding UpdateInfo.Updating}"
|
||||
DismissCommand="{Binding DismissUpdateCommand}"
|
||||
UpdateCommand="{Binding UpdateInstallerCommand}"
|
||||
DownloadProgress="{Binding UpdateInfo.DownloadProgress}"
|
||||
UpdateAvailable="{Binding UpdateInfo.UpdateAvailable}"
|
||||
CheckingForUpdate="{Binding UpdateInfo.CheckingForUpdates}"
|
||||
/>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,18 +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"/>
|
||||
<!-- 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. -->
|
||||
<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>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
// You will want to remove comments before using this template
|
||||
"latestVersion": "0.0", // the new version you are pushing
|
||||
"changes": [ // a list of chnages. These will be formated for you. Don't add leading bullets or such
|
||||
"changes",
|
||||
"go",
|
||||
"here"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user