diff --git a/build/Stride.Launcher.sln b/build/Stride.Launcher.sln
index 3bc538f023..a17b46d68b 100644
--- a/build/Stride.Launcher.sln
+++ b/build/Stride.Launcher.sln
@@ -1,9 +1,9 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28803.352
+# Visual Studio Version 18
+VisualStudioVersion = 18.1.11312.151 d18.0
MinimumVisualStudioVersion = 16.0
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Launcher", "..\sources\launcher\Stride.Launcher\Stride.Launcher.csproj", "{0F8BE30E-C41F-4747-B52B-D2D4E13EC6A2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Launcher", "..\sources\launcher\Stride.Launcher\Stride.Launcher.csproj", "{78695DE1-E621-45DF-975D-CFD407081E23}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core", "..\sources\core\Stride.Core\Stride.Core.csproj", "{BAC8FB10-95ED-4FBB-9925-F6F0E15BD936}"
EndProject
@@ -21,34 +21,32 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.IO", "..\source
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.MicroThreading", "..\sources\core\Stride.Core.MicroThreading\Stride.Core.MicroThreading.csproj", "{076940AD-70F3-47A8-827C-8E722714F937}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Presentation.Wpf", "..\sources\presentation\Stride.Core.Presentation.Wpf\Stride.Core.Presentation.Wpf.csproj", "{61E90191-22FF-4ADC-AFD0-FAB662589AB4}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Translation", "..\sources\core\Stride.Core.Translation\Stride.Core.Translation.csproj", "{8A23DF78-B3D6-41BD-BA50-19D0FBE4AB69}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Presentation.Dialogs", "..\sources\presentation\Stride.Core.Presentation.Dialogs\Stride.Core.Presentation.Dialogs.csproj", "{5EB0493A-076D-4488-AF08-D812FB3FDF7C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Packages", "..\sources\assets\Stride.Core.Packages\Stride.Core.Packages.csproj", "{1F5FBA04-C334-41C2-895A-ACC4B786F99E}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Translation", "..\sources\core\Stride.Core.Translation\Stride.Core.Translation.csproj", "{8A23DF78-B3D6-41BD-BA50-19D0FBE4AB69}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Presentation", "..\sources\presentation\Stride.Core.Presentation\Stride.Core.Presentation.csproj", "{0C63EF8B-26F9-4511-9FC5-7431DE9657D6}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Translation.Presentation", "..\sources\presentation\Stride.Core.Translation.Presentation\Stride.Core.Translation.Presentation.csproj", "{7B286D71-5143-4A08-B9DE-113B310A3F0C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.CompilerServices", "..\sources\core\Stride.Core.CompilerServices\Stride.Core.CompilerServices.csproj", "{ADE0E241-CBDD-48C3-8F50-98FFE76C03C8}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Packages", "..\sources\assets\Stride.Core.Packages\Stride.Core.Packages.csproj", "{1F5FBA04-C334-41C2-895A-ACC4B786F99E}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10-CoreRuntime", "10-CoreRuntime", "{706260A8-86D4-432D-9FE0-F312863288F5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "30-CoreDesign", "30-CoreDesign", "{FE721A31-DF09-4E33-B791-BEC6C9E1C6F1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "50-Presentation", "50-Presentation", "{3BC606D7-27B3-41FA-8FB3-9D56AC8B4DD7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stride.Core.Presentation", "..\sources\presentation\Stride.Core.Presentation\Stride.Core.Presentation.csproj", "{0C63EF8B-26F9-4511-9FC5-7431DE9657D6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Stride.Core.Presentation.Avalonia", "..\sources\presentation\Stride.Core.Presentation.Avalonia\Stride.Core.Presentation.Avalonia.csproj", "{3B613E66-671E-4049-8EF5-43CDAA549210}"
EndProject
Global
- GlobalSection(SharedMSBuildProjectFiles) = preSolution
- ..\sources\assets\Stride.Core.Assets.Yaml\Stride.Core.Assets.Yaml.projitems*{0f8be30e-c41f-4747-b52b-d2d4e13ec6a2}*SharedItemsImports = 5
- ..\sources\editor\Stride.Core.MostRecentlyUsedFiles\Stride.Core.MostRecentlyUsedFiles.projitems*{0f8be30e-c41f-4747-b52b-d2d4e13ec6a2}*SharedItemsImports = 5
- ..\sources\editor\Stride.Editor.CrashReport\Stride.Editor.CrashReport.projitems*{0f8be30e-c41f-4747-b52b-d2d4e13ec6a2}*SharedItemsImports = 5
- ..\sources\editor\Stride.PrivacyPolicy\Stride.PrivacyPolicy.projitems*{0f8be30e-c41f-4747-b52b-d2d4e13ec6a2}*SharedItemsImports = 5
- EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {0F8BE30E-C41F-4747-B52B-D2D4E13EC6A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0F8BE30E-C41F-4747-B52B-D2D4E13EC6A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0F8BE30E-C41F-4747-B52B-D2D4E13EC6A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0F8BE30E-C41F-4747-B52B-D2D4E13EC6A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {78695DE1-E621-45DF-975D-CFD407081E23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {78695DE1-E621-45DF-975D-CFD407081E23}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {78695DE1-E621-45DF-975D-CFD407081E23}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {78695DE1-E621-45DF-975D-CFD407081E23}.Release|Any CPU.Build.0 = Release|Any CPU
{BAC8FB10-95ED-4FBB-9925-F6F0E15BD936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAC8FB10-95ED-4FBB-9925-F6F0E15BD936}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BAC8FB10-95ED-4FBB-9925-F6F0E15BD936}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -81,22 +79,10 @@ Global
{076940AD-70F3-47A8-827C-8E722714F937}.Debug|Any CPU.Build.0 = Debug|Any CPU
{076940AD-70F3-47A8-827C-8E722714F937}.Release|Any CPU.ActiveCfg = Release|Any CPU
{076940AD-70F3-47A8-827C-8E722714F937}.Release|Any CPU.Build.0 = Release|Any CPU
- {61E90191-22FF-4ADC-AFD0-FAB662589AB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {61E90191-22FF-4ADC-AFD0-FAB662589AB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {61E90191-22FF-4ADC-AFD0-FAB662589AB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {61E90191-22FF-4ADC-AFD0-FAB662589AB4}.Release|Any CPU.Build.0 = Release|Any CPU
- {5EB0493A-076D-4488-AF08-D812FB3FDF7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {5EB0493A-076D-4488-AF08-D812FB3FDF7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {5EB0493A-076D-4488-AF08-D812FB3FDF7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {5EB0493A-076D-4488-AF08-D812FB3FDF7C}.Release|Any CPU.Build.0 = Release|Any CPU
{8A23DF78-B3D6-41BD-BA50-19D0FBE4AB69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A23DF78-B3D6-41BD-BA50-19D0FBE4AB69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A23DF78-B3D6-41BD-BA50-19D0FBE4AB69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A23DF78-B3D6-41BD-BA50-19D0FBE4AB69}.Release|Any CPU.Build.0 = Release|Any CPU
- {7B286D71-5143-4A08-B9DE-113B310A3F0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7B286D71-5143-4A08-B9DE-113B310A3F0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7B286D71-5143-4A08-B9DE-113B310A3F0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7B286D71-5143-4A08-B9DE-113B310A3F0C}.Release|Any CPU.Build.0 = Release|Any CPU
{1F5FBA04-C334-41C2-895A-ACC4B786F99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F5FBA04-C334-41C2-895A-ACC4B786F99E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F5FBA04-C334-41C2-895A-ACC4B786F99E}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -105,11 +91,36 @@ Global
{0C63EF8B-26F9-4511-9FC5-7431DE9657D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C63EF8B-26F9-4511-9FC5-7431DE9657D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C63EF8B-26F9-4511-9FC5-7431DE9657D6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ADE0E241-CBDD-48C3-8F50-98FFE76C03C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ADE0E241-CBDD-48C3-8F50-98FFE76C03C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ADE0E241-CBDD-48C3-8F50-98FFE76C03C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ADE0E241-CBDD-48C3-8F50-98FFE76C03C8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B613E66-671E-4049-8EF5-43CDAA549210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B613E66-671E-4049-8EF5-43CDAA549210}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B613E66-671E-4049-8EF5-43CDAA549210}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B613E66-671E-4049-8EF5-43CDAA549210}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {BAC8FB10-95ED-4FBB-9925-F6F0E15BD936} = {706260A8-86D4-432D-9FE0-F312863288F5}
+ {1F01E12A-50B7-4092-B30A-DEE9638FB753} = {706260A8-86D4-432D-9FE0-F312863288F5}
+ {53CDAFA0-30DF-404C-AAF0-652CA4047605} = {FE721A31-DF09-4E33-B791-BEC6C9E1C6F1}
+ {A7AE1C3F-CDC6-42DC-B4C1-5F7150661984} = {FE721A31-DF09-4E33-B791-BEC6C9E1C6F1}
+ {CA70C887-2A83-45DF-82EB-2DFFA8841B7B} = {706260A8-86D4-432D-9FE0-F312863288F5}
+ {CEF8B221-56E0-4777-88C1-9E3F9F3D1D3D} = {FE721A31-DF09-4E33-B791-BEC6C9E1C6F1}
+ {B6687100-3D8C-428C-8288-84607D9D5EDF} = {706260A8-86D4-432D-9FE0-F312863288F5}
+ {076940AD-70F3-47A8-827C-8E722714F937} = {706260A8-86D4-432D-9FE0-F312863288F5}
+ {8A23DF78-B3D6-41BD-BA50-19D0FBE4AB69} = {FE721A31-DF09-4E33-B791-BEC6C9E1C6F1}
+ {0C63EF8B-26F9-4511-9FC5-7431DE9657D6} = {3BC606D7-27B3-41FA-8FB3-9D56AC8B4DD7}
+ {ADE0E241-CBDD-48C3-8F50-98FFE76C03C8} = {706260A8-86D4-432D-9FE0-F312863288F5}
+ {3B613E66-671E-4049-8EF5-43CDAA549210} = {3BC606D7-27B3-41FA-8FB3-9D56AC8B4DD7}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {04241BED-1662-4690-BA56-15C99A840CFE}
EndGlobalSection
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ ..\sources\editor\Stride.Core.MostRecentlyUsedFiles\Stride.Core.MostRecentlyUsedFiles.projitems*{78695de1-e621-45df-975d-cfd407081e23}*SharedItemsImports = 5
+ EndGlobalSection
EndGlobal
diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props
index d80c39307b..0671dd6d05 100644
--- a/sources/Directory.Packages.props
+++ b/sources/Directory.Packages.props
@@ -118,7 +118,8 @@
- 11.0.6
+ 11.3.9
+ 11.3.9
@@ -127,6 +128,9 @@
+
+
+
@@ -148,4 +152,4 @@
-
\ No newline at end of file
+
diff --git a/sources/core/Stride.Core.Yaml.Tests/Stride.Core.Yaml.Tests.csproj b/sources/core/Stride.Core.Yaml.Tests/Stride.Core.Yaml.Tests.csproj
index 1977222ae3..f4b8d9af63 100644
--- a/sources/core/Stride.Core.Yaml.Tests/Stride.Core.Yaml.Tests.csproj
+++ b/sources/core/Stride.Core.Yaml.Tests/Stride.Core.Yaml.Tests.csproj
@@ -10,6 +10,7 @@
LinuxTools;WindowsTools
+
all
diff --git a/sources/launcher/Stride.Launcher/App.axaml b/sources/launcher/Stride.Launcher/App.axaml
new file mode 100644
index 0000000000..a31c2191fe
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/App.axaml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 12
+ 0,0,0,20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/launcher/Stride.Launcher/App.axaml.cs b/sources/launcher/Stride.Launcher/App.axaml.cs
new file mode 100644
index 0000000000..0c3a8f7f3d
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/App.axaml.cs
@@ -0,0 +1,66 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Data.Core.Plugins;
+using Avalonia.Markup.Xaml;
+using Stride.Core.Presentation.Avalonia.Services;
+using Stride.Core.Presentation.ViewModels;
+using Stride.Launcher.ViewModels;
+using Stride.Launcher.Views;
+
+namespace Stride.Launcher;
+
+public partial class App : Application
+{
+ internal readonly CancellationTokenSource cts = new();
+
+ internal MainWindow? MainWindow { get; private set; }
+
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ // Line below is needed to remove Avalonia data validation.
+ // Without this line you will get duplicate validations from both Avalonia and CT
+ BindingPlugins.DataValidators.RemoveAt(0);
+
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = MainWindow = new()
+ {
+ DataContext = InitializeMainViewModel()
+ };
+ }
+ else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
+ {
+ // don't remove; also used by visual designer.
+ singleViewPlatform.MainView = new MainView
+ {
+ DataContext = InitializeMainViewModel()
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+
+ private static MainViewModel InitializeMainViewModel()
+ {
+ return new(InitializeServiceProvider());
+ }
+
+ private static IViewModelServiceProvider InitializeServiceProvider()
+ {
+ var dispatcherService = DispatcherService.Create();
+ var services = new object[]
+ {
+ dispatcherService,
+ new DialogService(dispatcherService) { ApplicationName = Launcher.ApplicationName }
+ };
+ return new ViewModelServiceProvider(services);
+ }
+}
diff --git a/sources/launcher/Stride.Launcher/App.xaml b/sources/launcher/Stride.Launcher/App.xaml
deleted file mode 100644
index dc853949d5..0000000000
--- a/sources/launcher/Stride.Launcher/App.xaml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sources/launcher/Stride.Launcher/App.xaml.cs b/sources/launcher/Stride.Launcher/App.xaml.cs
deleted file mode 100644
index 0ed6af7ada..0000000000
--- a/sources/launcher/Stride.Launcher/App.xaml.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-namespace Stride.LauncherApp
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App
- {
- }
-}
diff --git a/sources/launcher/Stride.Launcher/Assets/CrashReportImage.png b/sources/launcher/Stride.Launcher/Assets/CrashReportImage.png
new file mode 100644
index 0000000000..b1bf3de6f6
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Assets/CrashReportImage.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:36917dc9595f57e66f7d9692cd77fc19e8cc0f7eb31d76fe117d877ad72b2d31
+size 3019
diff --git a/sources/launcher/Stride.Launcher/Resources/EditorIcon.png b/sources/launcher/Stride.Launcher/Assets/Images/EditorIcon.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/EditorIcon.png
rename to sources/launcher/Stride.Launcher/Assets/Images/EditorIcon.png
diff --git a/sources/launcher/Stride.Launcher/Resources/chat-16.png b/sources/launcher/Stride.Launcher/Assets/Images/chat.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/chat-16.png
rename to sources/launcher/Stride.Launcher/Assets/Images/chat.png
diff --git a/sources/launcher/Stride.Launcher/Resources/delete-26-dark.png b/sources/launcher/Stride.Launcher/Assets/Images/delete.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/delete-26-dark.png
rename to sources/launcher/Stride.Launcher/Assets/Images/delete.png
diff --git a/sources/launcher/Stride.Launcher/Resources/discord.png b/sources/launcher/Stride.Launcher/Assets/Images/discord.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/discord.png
rename to sources/launcher/Stride.Launcher/Assets/Images/discord.png
diff --git a/sources/launcher/Stride.Launcher/Resources/download-26-dark.png b/sources/launcher/Stride.Launcher/Assets/Images/download.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/download-26-dark.png
rename to sources/launcher/Stride.Launcher/Assets/Images/download.png
diff --git a/sources/launcher/Stride.Launcher/Resources/facebook_24.png b/sources/launcher/Stride.Launcher/Assets/Images/facebook.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/facebook_24.png
rename to sources/launcher/Stride.Launcher/Assets/Images/facebook.png
diff --git a/sources/launcher/Stride.Launcher/Resources/getting-started.png b/sources/launcher/Stride.Launcher/Assets/Images/getting-started.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/getting-started.png
rename to sources/launcher/Stride.Launcher/Assets/Images/getting-started.png
diff --git a/sources/launcher/Stride.Launcher/Resources/github.png b/sources/launcher/Stride.Launcher/Assets/Images/github.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/github.png
rename to sources/launcher/Stride.Launcher/Assets/Images/github.png
diff --git a/sources/launcher/Stride.Launcher/Resources/issues.png b/sources/launcher/Stride.Launcher/Assets/Images/issues.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/issues.png
rename to sources/launcher/Stride.Launcher/Assets/Images/issues.png
diff --git a/sources/launcher/Stride.Launcher/Resources/list-26.png b/sources/launcher/Stride.Launcher/Assets/Images/list.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/list-26.png
rename to sources/launcher/Stride.Launcher/Assets/Images/list.png
diff --git a/sources/launcher/Stride.Launcher/Resources/news.png b/sources/launcher/Stride.Launcher/Assets/Images/news.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/news.png
rename to sources/launcher/Stride.Launcher/Assets/Images/news.png
diff --git a/sources/launcher/Stride.Launcher/Resources/note-26-dark.png b/sources/launcher/Stride.Launcher/Assets/Images/note.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/note-26-dark.png
rename to sources/launcher/Stride.Launcher/Assets/Images/note.png
diff --git a/sources/launcher/Stride.Launcher/Resources/opencollective_24.png b/sources/launcher/Stride.Launcher/Assets/Images/opencollective.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/opencollective_24.png
rename to sources/launcher/Stride.Launcher/Assets/Images/opencollective.png
diff --git a/sources/launcher/Stride.Launcher/Resources/recent-projects.png b/sources/launcher/Stride.Launcher/Assets/Images/recent-projects.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/recent-projects.png
rename to sources/launcher/Stride.Launcher/Assets/Images/recent-projects.png
diff --git a/sources/launcher/Stride.Launcher/Resources/reddit_24.png b/sources/launcher/Stride.Launcher/Assets/Images/reddit.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/reddit_24.png
rename to sources/launcher/Stride.Launcher/Assets/Images/reddit.png
diff --git a/sources/launcher/Stride.Launcher/Resources/roadmap.png b/sources/launcher/Stride.Launcher/Assets/Images/roadmap.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/roadmap.png
rename to sources/launcher/Stride.Launcher/Assets/Images/roadmap.png
diff --git a/sources/launcher/Stride.Launcher/Assets/Images/robot.png b/sources/launcher/Stride.Launcher/Assets/Images/robot.png
new file mode 100644
index 0000000000..9e25c59043
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Assets/Images/robot.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:876b4471b32cb4e9fc354e9d84519b06ca03cc6cdbc56f3780374e511512efdf
+size 362967
diff --git a/sources/launcher/Stride.Launcher/Resources/showcase.png b/sources/launcher/Stride.Launcher/Assets/Images/showcase.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/showcase.png
rename to sources/launcher/Stride.Launcher/Assets/Images/showcase.png
diff --git a/sources/launcher/Stride.Launcher/Resources/survey.png b/sources/launcher/Stride.Launcher/Assets/Images/survey.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/survey.png
rename to sources/launcher/Stride.Launcher/Assets/Images/survey.png
diff --git a/sources/launcher/Stride.Launcher/Resources/switch-version.png b/sources/launcher/Stride.Launcher/Assets/Images/switch-version.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/switch-version.png
rename to sources/launcher/Stride.Launcher/Assets/Images/switch-version.png
diff --git a/sources/launcher/Stride.Launcher/Resources/twitch_24.png b/sources/launcher/Stride.Launcher/Assets/Images/twitch.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/twitch_24.png
rename to sources/launcher/Stride.Launcher/Assets/Images/twitch.png
diff --git a/sources/launcher/Stride.Launcher/Resources/update.png b/sources/launcher/Stride.Launcher/Assets/Images/update.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/update.png
rename to sources/launcher/Stride.Launcher/Assets/Images/update.png
diff --git a/sources/launcher/Stride.Launcher/Resources/upgrade-16.png b/sources/launcher/Stride.Launcher/Assets/Images/upgrade.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/upgrade-16.png
rename to sources/launcher/Stride.Launcher/Assets/Images/upgrade.png
diff --git a/sources/launcher/Stride.Launcher/Resources/visual-studio.png b/sources/launcher/Stride.Launcher/Assets/Images/visual-studio.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/visual-studio.png
rename to sources/launcher/Stride.Launcher/Assets/Images/visual-studio.png
diff --git a/sources/launcher/Stride.Launcher/Resources/xtwitter_24.png b/sources/launcher/Stride.Launcher/Assets/Images/xtwitter.png
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/xtwitter_24.png
rename to sources/launcher/Stride.Launcher/Assets/Images/xtwitter.png
diff --git a/sources/launcher/Stride.Launcher/Resources/Launcher.ico b/sources/launcher/Stride.Launcher/Assets/Launcher.ico
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/Launcher.ico
rename to sources/launcher/Stride.Launcher/Assets/Launcher.ico
diff --git a/sources/launcher/Stride.Launcher/Resources/Strings.Designer.cs b/sources/launcher/Stride.Launcher/Assets/Localization/Strings.Designer.cs
similarity index 99%
rename from sources/launcher/Stride.Launcher/Resources/Strings.Designer.cs
rename to sources/launcher/Stride.Launcher/Assets/Localization/Strings.Designer.cs
index 130b285152..4544ed1c8b 100644
--- a/sources/launcher/Stride.Launcher/Resources/Strings.Designer.cs
+++ b/sources/launcher/Stride.Launcher/Assets/Localization/Strings.Designer.cs
@@ -8,7 +8,7 @@
//
//------------------------------------------------------------------------------
-namespace Stride.LauncherApp.Resources {
+namespace Stride.Launcher.Assets.Localization {
using System;
@@ -39,7 +39,7 @@ internal Strings() {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Stride.LauncherApp.Resources.Strings", typeof(Strings).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Stride.Launcher.Assets.Localization.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -139,9 +139,9 @@ public static string ButtonForums {
///
/// Looks up a localized string similar to Fork on GitHub.
///
- public static string ButtonGithub {
+ public static string ButtonGitHub {
get {
- return ResourceManager.GetString("ButtonGithub", resourceCulture);
+ return ResourceManager.GetString("ButtonGitHub", resourceCulture);
}
}
diff --git a/sources/launcher/Stride.Launcher/Resources/Strings.ja-JP.resx b/sources/launcher/Stride.Launcher/Assets/Localization/Strings.ja-JP.resx
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/Strings.ja-JP.resx
rename to sources/launcher/Stride.Launcher/Assets/Localization/Strings.ja-JP.resx
diff --git a/sources/launcher/Stride.Launcher/Resources/Strings.resx b/sources/launcher/Stride.Launcher/Assets/Localization/Strings.resx
similarity index 99%
rename from sources/launcher/Stride.Launcher/Resources/Strings.resx
rename to sources/launcher/Stride.Launcher/Assets/Localization/Strings.resx
index b75c9f282f..a783719fad 100644
--- a/sources/launcher/Stride.Launcher/Resources/Strings.resx
+++ b/sources/launcher/Stride.Launcher/Assets/Localization/Strings.resx
@@ -133,7 +133,7 @@
Discuss about Stride
/!\ Text must be short
-
+
Fork on GitHub
/!\ Text must be short
diff --git a/sources/launcher/Stride.Launcher/Resources/Urls.Designer.cs b/sources/launcher/Stride.Launcher/Assets/Localization/Urls.Designer.cs
similarity index 97%
rename from sources/launcher/Stride.Launcher/Resources/Urls.Designer.cs
rename to sources/launcher/Stride.Launcher/Assets/Localization/Urls.Designer.cs
index d61a3b7c68..0be689bd72 100644
--- a/sources/launcher/Stride.Launcher/Resources/Urls.Designer.cs
+++ b/sources/launcher/Stride.Launcher/Assets/Localization/Urls.Designer.cs
@@ -8,7 +8,7 @@
//
//------------------------------------------------------------------------------
-namespace Stride.LauncherApp.Resources {
+namespace Stride.Launcher.Assets.Localization {
using System;
@@ -39,7 +39,7 @@ internal Urls() {
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Stride.LauncherApp.Resources.Urls", typeof(Urls).Assembly);
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Stride.Launcher.Assets.Localization.Urls", typeof(Urls).Assembly);
resourceMan = temp;
}
return resourceMan;
@@ -108,9 +108,9 @@ public static string GettingStarted {
///
/// Looks up a localized string similar to https://github.com/stride3d/stride/.
///
- public static string Github {
+ public static string GitHub {
get {
- return ResourceManager.GetString("Github", resourceCulture);
+ return ResourceManager.GetString("GitHub", resourceCulture);
}
}
diff --git a/sources/launcher/Stride.Launcher/Resources/Urls.ja-JP.resx b/sources/launcher/Stride.Launcher/Assets/Localization/Urls.ja-JP.resx
similarity index 100%
rename from sources/launcher/Stride.Launcher/Resources/Urls.ja-JP.resx
rename to sources/launcher/Stride.Launcher/Assets/Localization/Urls.ja-JP.resx
diff --git a/sources/launcher/Stride.Launcher/Resources/Urls.resx b/sources/launcher/Stride.Launcher/Assets/Localization/Urls.resx
similarity index 99%
rename from sources/launcher/Stride.Launcher/Resources/Urls.resx
rename to sources/launcher/Stride.Launcher/Assets/Localization/Urls.resx
index 8ab986f887..ec6e7404d4 100644
--- a/sources/launcher/Stride.Launcher/Resources/Urls.resx
+++ b/sources/launcher/Stride.Launcher/Assets/Localization/Urls.resx
@@ -134,7 +134,7 @@
https://doc.stride3d.net/{0}/studio_getting_started_links.txt
{0}: the major version of Stride (eg. 1.2)
-
+
https://github.com/stride3d/stride/
@@ -161,4 +161,4 @@
https://visualstudio.microsoft.com/downloads
-
+
\ No newline at end of file
diff --git a/sources/launcher/Stride.Launcher/Constants.cs b/sources/launcher/Stride.Launcher/Constants.cs
new file mode 100644
index 0000000000..b50f04c2ef
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Constants.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+namespace Stride.Launcher;
+
+internal struct Names
+{
+ public const string GameStudio = nameof(GameStudio);
+ public const string Stride = nameof(Stride);
+ public const string Xenko = nameof(Xenko);
+}
+
+internal struct GameStudioNames
+{
+ public const string Stride = $"{Names.Stride}.{Names.GameStudio}";
+ public const string StrideAvalonia = $"{Names.Stride}.{Names.GameStudio}.Avalonia.Desktop";
+ public const string Xenko = $"{Names.Xenko}.{Names.GameStudio}";
+}
diff --git a/sources/launcher/Stride.Launcher/Crash/CrashReportArgs.cs b/sources/launcher/Stride.Launcher/Crash/CrashReportArgs.cs
new file mode 100644
index 0000000000..5933afe724
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Crash/CrashReportArgs.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+namespace Stride.Crash;
+
+internal enum CrashLocation
+{
+ Main,
+ UnhandledException
+}
+
+internal record CrashReportArgs
+{
+ public required Exception Exception { get; set; }
+ public required CrashLocation Location { get; set; }
+ public string[] Logs { get; set; } = [];
+ public string? ThreadName { get; set; }
+}
diff --git a/sources/launcher/Stride.Launcher/Crash/CrashReportData.cs b/sources/launcher/Stride.Launcher/Crash/CrashReportData.cs
new file mode 100644
index 0000000000..6784925069
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Crash/CrashReportData.cs
@@ -0,0 +1,52 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using System.Text;
+using System.Text.Json;
+
+namespace Stride.Crash;
+
+public sealed class CrashReportData
+{
+ public List<(string key, object? value)> Data = [];
+
+ public object? this[string key]
+ {
+ get => Data.Find(p => p.key == key).value;
+ set
+ {
+ if (value == null)
+ return;
+
+ int num = -1;
+ foreach (var current in Data)
+ {
+ if (current.key == key)
+ {
+ num = Data.IndexOf(current);
+ break;
+ }
+ }
+ if (num != -1)
+ {
+ Data[num] = (key, value);
+ }
+ else
+ {
+ Data.Add((key, value));
+ }
+ }
+ }
+
+ public string ToJson() => JsonSerializer.Serialize(Data.ToDictionary());
+
+ public override string ToString()
+ {
+ StringBuilder val = new();
+ foreach (var (key, value) in Data)
+ {
+ val.AppendLine($"{key}: {value}");
+ }
+ return val.ToString();
+ }
+}
diff --git a/sources/launcher/Stride.Launcher/Crash/CrashReportViewModel.cs b/sources/launcher/Stride.Launcher/Crash/CrashReportViewModel.cs
new file mode 100644
index 0000000000..d8f891399f
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Crash/CrashReportViewModel.cs
@@ -0,0 +1,118 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+using Stride.Core.Extensions;
+using Stride.Core.Presentation.Avalonia.Services;
+using Stride.Core.Presentation.Commands;
+using Stride.Core.Presentation.Services;
+using Stride.Core.Presentation.ViewModels;
+
+namespace Stride.Crash.ViewModels;
+
+internal sealed class CrashReportViewModel : ViewModelBase
+{
+ private readonly string applicationName;
+ private readonly CancellationTokenSource exitToken;
+ private readonly Func setClipboard;
+
+ private bool isReportVisible;
+
+ public CrashReportViewModel(string applicationName, CrashReportArgs args, Func setClipboard, CancellationTokenSource exitToken)
+ : base(new ViewModelServiceProvider())
+ {
+ this.applicationName = applicationName;
+ this.exitToken = exitToken;
+ this.setClipboard = setClipboard;
+
+ var dispatcher = DispatcherService.Create();
+ ServiceProvider.RegisterService(dispatcher);
+ ServiceProvider.RegisterService(new DialogService(dispatcher) { ApplicationName = applicationName });
+
+ Report = ComputeReport(args);
+
+ CopyReportCommand = new AnonymousTaskCommand(ServiceProvider, OnCopyReport);
+ CloseCommand = new AnonymousCommand(ServiceProvider, OnClose);
+ OpenIssueCommand = new AnonymousTaskCommand(ServiceProvider, OnOpenIssue);
+ ViewReportCommand = new AnonymousCommand(ServiceProvider, OnViewReport);
+ }
+
+ public string ApplicationName => applicationName;
+
+ public bool IsReportVisible
+ {
+ get => isReportVisible;
+ set => SetValue(ref isReportVisible, value);
+ }
+
+ public CrashReportData Report { get; }
+
+ public ICommandBase CopyReportCommand { get; }
+ public ICommandBase CloseCommand { get; }
+ public ICommandBase OpenIssueCommand { get; }
+ public ICommandBase ViewReportCommand { get; }
+
+ private void OnClose()
+ {
+ exitToken.Cancel();
+ }
+
+ private Task OnCopyReport()
+ {
+ return setClipboard.Invoke(Report.ToJson());
+ }
+
+ private async Task OnOpenIssue()
+ {
+ try
+ {
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = "https://github.com/stride3d/stride/issues/new?labels=bug&template=bug_report.md&",
+ UseShellExecute = true
+ });
+ }
+ // FIXME: catch only specific exceptions?
+ catch (Exception)
+ {
+ DialogService.MainWindow!.Topmost = false;
+ // FIXME: localize resource string
+ await ServiceProvider.Get().MessageBoxAsync("An error occurred while trying to open a web browser", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void OnViewReport()
+ {
+ IsReportVisible = true;
+ }
+
+ private CrashReportData ComputeReport(CrashReportArgs args)
+ {
+ return new()
+ {
+ ["Application"] = applicationName,
+ ["ThreadName"] = args.ThreadName,
+#if DEBUG
+ ["ProcessID"] = Environment.ProcessId,
+ ["CurrentDirectory"] = Environment.CurrentDirectory,
+#endif
+ ["OsArch"] = Environment.Is64BitOperatingSystem ? "x64" : "x86",
+ ["OsVersion"] = Environment.OSVersion,
+ ["ProcessorCount"] = Environment.ProcessorCount,
+ ["Exception"] = args.Exception.FormatFull(),
+ ["LastLogs"] = FormatLogs(args.Logs),
+ };
+
+ static string FormatLogs(string[] logs)
+ {
+ var builder = new StringBuilder();
+ for (var i = 0; i < logs.Length; i++)
+ {
+ var log = logs[i];
+ builder.AppendLine($"{i + 1}: {log}");
+ }
+ return builder.ToString();
+ }
+ }
+}
diff --git a/sources/launcher/Stride.Launcher/Crash/CrashReportWindow.axaml b/sources/launcher/Stride.Launcher/Crash/CrashReportWindow.axaml
new file mode 100644
index 0000000000..d2119585a6
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Crash/CrashReportWindow.axaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+ Unfortunately, has crashed.
+ Please help us improve Stride by sending information about this crash through Github Issues.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/launcher/Stride.Launcher/Crash/CrashReportWindow.axaml.cs b/sources/launcher/Stride.Launcher/Crash/CrashReportWindow.axaml.cs
new file mode 100644
index 0000000000..9bdb26bfc5
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Crash/CrashReportWindow.axaml.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using Avalonia.Controls;
+using Avalonia.Input;
+
+namespace Stride.Crash;
+
+public partial class CrashReportWindow : Window
+{
+ public CrashReportWindow()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (e.Key == Key.Escape)
+ {
+ Close();
+ }
+ }
+}
diff --git a/sources/launcher/Stride.Launcher/CrashReport/CrashReportHelper.cs b/sources/launcher/Stride.Launcher/CrashReport/CrashReportHelper.cs
deleted file mode 100644
index 996fb09af5..0000000000
--- a/sources/launcher/Stride.Launcher/CrashReport/CrashReportHelper.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Globalization;
-using System.Threading;
-using System.Windows.Threading;
-using Stride.Core.Extensions;
-using Stride.Core.Windows;
-using Stride.CrashReport;
-using Stride.Editor.CrashReport;
-
-namespace Stride.LauncherApp.CrashReport
-{
- public static class CrashReportHelper
- {
- private static bool terminating;
-
- public static void HandleException(Dispatcher dispatcher, Exception exception)
- {
- if (exception == null)
- return;
-
- //prevent multiple crash reports
- if (terminating)
- return;
-
- terminating = true;
-
- var englishCulture = new CultureInfo("en-US");
- var crashLogThread = new Thread(CrashReport) { CurrentUICulture = englishCulture, CurrentCulture = englishCulture };
- crashLogThread.Start(new CrashReportArgs(exception, dispatcher));
- crashLogThread.Join();
- }
-
- [STAThread]
- private static void CrashReport(object data)
- {
- var args = (CrashReportArgs)data;
-
- args.Dispatcher?.InvokeAsync(() => Thread.CurrentThread.Join());
-
- SendReport(args.Exception.FormatFull());
-
- Environment.Exit(0);
- }
-
- private static void SendReport(string exceptionMessage)
- {
- var crashReport = new CrashReportData
- {
- ["Application"] = "Launcher",
- ["UserEmail"] = "",
-
- ["UserMessage"] = "",
- ["CurrentDirectory"] = Environment.CurrentDirectory,
- ["CommandArgs"] = string.Join(" ", AppHelper.GetCommandLineArgs()),
- ["OsVersion"] = $"{Environment.OSVersion} {(Environment.Is64BitOperatingSystem ? "x64" : "x86")}",
- ["ProcessorCount"] = Environment.ProcessorCount.ToString(),
- ["Exception"] = exceptionMessage
- };
-
- var videoConfig = AppHelper.GetVideoConfig();
- foreach (var conf in videoConfig)
- {
- crashReport.Data.Add(Tuple.Create(conf.Key, conf.Value));
- }
-
- var reporter = new CrashReportForm(crashReport, new CrashReportSettings());
- reporter.ShowDialog();
- }
-
- private record CrashReportArgs(Exception Exception, Dispatcher Dispatcher);
- }
-}
diff --git a/sources/launcher/Stride.Launcher/CrashReport/CrashReportSettings.cs b/sources/launcher/Stride.Launcher/CrashReport/CrashReportSettings.cs
deleted file mode 100644
index ef49fc54e2..0000000000
--- a/sources/launcher/Stride.Launcher/CrashReport/CrashReportSettings.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using Stride.Editor.CrashReport;
-using Stride.LauncherApp.Services;
-
-namespace Stride.LauncherApp
-{
- internal class CrashReportSettings : ICrashEmailSetting
- {
- public CrashReportSettings()
- {
- Email = GameStudioSettings.CrashReportEmail;
- StoreCrashEmail = !string.IsNullOrEmpty(Email);
- }
-
- public bool StoreCrashEmail { get; set; }
-
- public string Email { get; set; }
-
- public void Save()
- {
- GameStudioSettings.CrashReportEmail = Email;
- }
- }
-}
diff --git a/sources/launcher/Stride.Launcher/Launcher.cs b/sources/launcher/Stride.Launcher/Launcher.cs
index 5cdd1cf925..c83d1cfd8c 100644
--- a/sources/launcher/Stride.Launcher/Launcher.cs
+++ b/sources/launcher/Stride.Launcher/Launcher.cs
@@ -1,238 +1,241 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
-using System.IO;
-using System.Linq;
using System.Reflection;
-using System.Windows;
-using Stride.Core.Annotations;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
using Stride.Core.Assets.Editor;
using Stride.Core.Extensions;
using Stride.Core.IO;
using Stride.Core.Packages;
+using Stride.Core.Presentation.Avalonia.Windows;
+using Stride.Core.Presentation.Services;
using Stride.Core.Windows;
-using Stride.LauncherApp.CrashReport;
-using Stride.LauncherApp.Services;
-using Stride.Metrics;
-using Stride.PrivacyPolicy;
-using Dispatcher = System.Windows.Threading.Dispatcher;
-using MessageBox = System.Windows.MessageBox;
-
-namespace Stride.LauncherApp
-{
- ///
- /// Entry point class of the Launcher.
- ///
- public static class Launcher
- {
- internal static FileLock Mutex;
- internal static MetricsClient Metrics;
+using Stride.Crash;
+using Stride.Crash.ViewModels;
+using Stride.Launcher.Services;
- public const string ApplicationName = "Stride Launcher";
+namespace Stride.Launcher;
- ///
- /// The entry point function of the launcher.
- ///
- /// The process error code to return.
- [STAThread]
- public static int Main(string[] args)
- {
- // For now, we force culture to invariant one because GNU.Gettext.GettextResourceManager.GetSatelliteAssembly crashes when Assembly.Location is null
- CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
+internal static class Launcher
+{
+ private static int terminating;
+ internal static FileLock? Mutex;
- var arguments = ProcessArguments(args);
- var result = ProcessAction(arguments);
- return (int)result;
- }
+ public const string ApplicationName = "Stride Launcher";
- ///
- /// Returns path of Launcher (we can't use Assembly.GetEntryAssembly().Location in .NET Core, especially with self-publish).
- ///
- ///
- internal static string GetExecutablePath()
+ [STAThread]
+ public static LauncherErrorCode Main(string[] args)
+ {
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ try
{
- return Environment.ProcessPath;
+ var arguments = ProcessArguments(args);
+ return ProcessAction(arguments);
}
-
- ///
- /// Initializes a instance assuming the entry point assembly is located at the root of the store.
- ///
- /// A new instance of .
- [NotNull]
- internal static NugetStore InitializeNugetStore()
+ catch (Exception ex)
{
- var thisExeDirectory = new UFile(Assembly.GetEntryAssembly().Location).GetFullDirectory().ToOSPath();
- var store = new NugetStore(thisExeDirectory);
- return store;
+ HandleException(ex, CrashLocation.Main);
+ return LauncherErrorCode.ErrorWhileRunningServer;
}
+ }
- ///
- /// Displays a message to the user with OK and Cancel buttons, and returns whether the user cancelled.
- ///
- /// The message to display.
- /// True if the user answered OK, False otherwise.
- internal static bool DisplayMessage(string message)
- {
- var result = MessageBox.Show(message, "Stride", MessageBoxButton.YesNo, MessageBoxImage.Information);
- return result == MessageBoxResult.Yes;
- }
+ internal static NugetStore InitializeNugetStore()
+ {
+ var thisExeDirectory = new UFile(Assembly.GetEntryAssembly()!.Location).GetFullDirectory().ToOSPath();
+ var store = new NugetStore(thisExeDirectory);
+ return store;
+ }
- ///
- /// Displays an error message to the user with just an OK button.
- ///
- /// The message to display.
- internal static void DisplayError(string message)
- {
- MessageBox.Show(message, "Stride", MessageBoxButton.OK, MessageBoxImage.Error);
- }
+ private static LauncherErrorCode ProcessAction(LauncherArguments args)
+ {
+ var result = LauncherErrorCode.UnknownError;
- private static LauncherArguments ProcessArguments(string[] args)
+ try
{
- var result = new LauncherArguments
- {
- // Default action is to run the server
- Actions = new List { LauncherArguments.ActionType.Run }
- };
-
- foreach (var arg in args)
+ // Ensure to create parent of lock directory.
+ Directory.CreateDirectory(EditorPath.DefaultTempPath);
+ using (Mutex = FileLock.TryLock(Path.Combine(EditorPath.DefaultTempPath, "launcher.lock")))
{
- if (string.Equals(arg, "/Uninstall", StringComparison.InvariantCultureIgnoreCase))
+ if (Mutex is not null)
{
- // No other action possible when uninstalling.
- result.Actions.Clear();
- result.Actions.Add(LauncherArguments.ActionType.Uninstall);
+ Program.RunNewApp(AppMain);
+ }
+ else
+ {
+ DisplayError("An instance of Stride Launcher is already running.", MessageBoxImage.Warning);
+ result = LauncherErrorCode.ServerAlreadyRunning;
}
}
+ }
+ catch (Exception e)
+ {
+ DisplayError($"Cannot start the instance of the Stride Launcher due to the following exception:\n{e.Message}", MessageBoxImage.Error);
+ result = LauncherErrorCode.UnknownError;
+ }
- return result;
+ return result;
+
+ CancellationToken AppMain(App app)
+ {
+ _ = AppMainAsync(app.cts);
+ return app.cts.Token;
}
- private static LauncherErrorCode ProcessAction(LauncherArguments args)
+ async Task AppMainAsync(CancellationTokenSource cts)
{
- var result = LauncherErrorCode.UnknownError;
foreach (var action in args.Actions)
{
- switch (action)
+ result = action switch
{
- case LauncherArguments.ActionType.Run:
- result = TryRun();
- break;
- case LauncherArguments.ActionType.Uninstall:
- result = Uninstall();
- break;
- default:
- // Unknown action
- return LauncherErrorCode.UnknownError;
- }
- if (IsError(result))
- return result;
+ LauncherArguments.ActionType.Run => TryRun(cts),
+ LauncherArguments.ActionType.Uninstall => await UninstallAsync(cts),
+ _ => LauncherErrorCode.UnknownError,// Unknown action
+ };
+ if (result < LauncherErrorCode.Success)
+ break;
}
- return result;
}
- private static LauncherErrorCode TryRun()
+ static void DisplayError(string message, MessageBoxImage image)
{
- try
- {
- // Ensure to create parent of lock directory.
- Directory.CreateDirectory(EditorPath.DefaultTempPath);
- using (Mutex = FileLock.TryLock(Path.Combine(EditorPath.DefaultTempPath, "launcher.lock")))
- {
- if (Mutex != null)
- {
- return RunSingleInstance(false);
- }
+ // Note: because we are not running from the main loop, we have to start a new app
+ Program.RunNewApp(AppMain);
- MessageBox.Show("An instance of Stride Launcher is already running.", "Stride", MessageBoxButton.OK, MessageBoxImage.Exclamation);
- return LauncherErrorCode.ServerAlreadyRunning;
- }
- }
- catch (Exception e)
+ CancellationToken AppMain(Application app)
{
- DisplayError($"Cannot start the instance of the Stride Launcher due to the following exception:\n{e.Message}");
- return LauncherErrorCode.UnknownError;
+ var cts = new CancellationTokenSource();
+ _ = MessageBox.ShowAsync(ApplicationName, message, IDialogService.GetButtons(MessageBoxButton.OK), image).ContinueWith(_ => cts.Cancel());
+ return cts.Token;
}
}
+ }
- private static LauncherErrorCode RunSingleInstance(bool shouldStartHidden)
+ private static LauncherArguments ProcessArguments(string[] args)
+ {
+ var result = new LauncherArguments
{
- try
+ // Default action is to run the server
+ Actions = [LauncherArguments.ActionType.Run],
+ Args = args,
+ };
+
+ foreach (var arg in args)
+ {
+ if (string.Equals(arg, "/Uninstall", StringComparison.InvariantCultureIgnoreCase))
{
- // Only needed for Stride up to 2.x (and possibly 3.0): setup the StrideDir to make sure that it is passed to the underlying process (msbuild...etc.)
- Environment.SetEnvironmentVariable("SiliconStudioStrideDir", AppDomain.CurrentDomain.BaseDirectory);
- Environment.SetEnvironmentVariable("StrideDir", AppDomain.CurrentDomain.BaseDirectory);
+ // No other action possible when uninstalling.
+ result.Actions.Clear();
+ result.Actions.Add(LauncherArguments.ActionType.Uninstall);
+ }
+ }
- // We need to do that before starting recording metrics
- // TODO: we do not display Privacy Policy anymore from launcher, because it's either accepted from installer or shown again when a new version of GS with new Privacy Policy starts. Might want to reconsider that after the 2.0 free period
- PrivacyPolicyHelper.RestartApplication = SelfUpdater.RestartApplication;
- PrivacyPolicyHelper.EnsurePrivacyPolicyStride40();
+ return result;
+ }
- // Install Metrics for the launcher
- using (Metrics = new MetricsClient(CommonApps.StrideLauncherAppId))
- {
- // HACK: force resolve the presentation assembly prior to initializing the app. This is to fix an issue with XAML themes.
- // see issue PDX-2899
- var txt = new Core.Presentation.Controls.TextBox();
- GC.KeepAlive(txt); // prevent aggressive optimization from removing the line where we create the dummy TextBox.
+ private static LauncherErrorCode TryRun(CancellationTokenSource cts)
+ {
+ var mainWindow = ((IClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!).MainWindow!;
+ mainWindow.Closed += (_, _) => cts.Cancel();
+ mainWindow.Show();
+ return LauncherErrorCode.Success;
+ }
- var instance = new LauncherInstance();
- return instance.Run(shouldStartHidden);
- }
- }
- catch (Exception exception)
+ private static async Task UninstallAsync(CancellationTokenSource cts)
+ {
+ try
+ {
+ // Kill all running processes
+ var path = new UFile(Assembly.GetEntryAssembly()!.Location).GetFullDirectory().ToOSPath();
+ if (!await UninstallHelper.CloseProcessesInPathAsync(DisplayMessageAsync, "Stride", path))
+ return LauncherErrorCode.UninstallCancelled; // User cancelled
+
+ // Uninstall packages (they might have uninstall actions)
+ var store = new NugetStore(path);
+ foreach (var package in store.MainPackageIds.SelectMany(store.GetLocalPackages).FilterStrideMainPackages().ToList())
{
- CrashReportHelper.HandleException(Dispatcher.CurrentDispatcher, exception);
- return LauncherErrorCode.ErrorWhileRunningServer;
+ await store.UninstallPackage(package, null);
}
- }
- private static LauncherErrorCode Uninstall()
- {
- try
+ foreach (var remainingFiles in Directory.GetFiles(path, "*.lock").Concat(Directory.GetFiles(path, "*.old")))
{
- // Kill all running processes
- var path = new UFile(Assembly.GetEntryAssembly().Location).GetFullDirectory().ToOSPath();
- if (!UninstallHelper.CloseProcessesInPath(DisplayMessage, "Stride", path))
- return LauncherErrorCode.UninstallCancelled; // User cancelled
-
- // Uninstall packages (they might have uninstall actions)
- var store = new NugetStore(path);
- foreach (var package in store.MainPackageIds.SelectMany(store.GetLocalPackages).FilterStrideMainPackages().ToList())
+ try
{
- store.UninstallPackage(package, null).Wait();
+ File.Delete(remainingFiles);
}
-
- foreach (var remainingFiles in Directory.GetFiles(path, "*.lock").Concat(Directory.GetFiles(path, "*.old")))
+ catch (Exception e)
{
- try
- {
- File.Delete(remainingFiles);
- }
- catch (Exception e)
- {
- e.Ignore();
- }
+ e.Ignore();
}
+ }
- PrivacyPolicyHelper.RevokeAllPrivacyPolicy();
+ //PrivacyPolicyHelper.RevokeAllPrivacyPolicy(); // FIXME: xplat-launcher
- return LauncherErrorCode.Success;
- }
- catch (Exception)
+ return LauncherErrorCode.Success;
+ }
+ catch (Exception)
+ {
+ return LauncherErrorCode.ErrorWhileUninstalling;
+ }
+ finally
+ {
+ await cts.CancelAsync();
+ }
+
+ static async Task DisplayMessageAsync(string message)
+ {
+ var result = await MessageBox.ShowAsync(ApplicationName, message, IDialogService.GetButtons(MessageBoxButton.YesNo), MessageBoxImage.Information);
+ return result == (int)MessageBoxResult.Yes;
+ }
+ }
+
+ #region Crash
+
+ private static void CrashReport(CrashReportArgs args)
+ {
+ Program.RunNewApp(AppMain);
+
+ CancellationToken AppMain(Application app)
+ {
+ var cts = new CancellationTokenSource();
+ var window = new CrashReportWindow { Topmost = true };
+ window.DataContext = new CrashReportViewModel(ApplicationName, args, window.Clipboard!.SetTextAsync, cts);
+ window.Closed += (_, _) => cts.Cancel();
+ if (!window.IsVisible)
{
- return LauncherErrorCode.ErrorWhileUninstalling;
+ window.Show();
}
+ ((IClassicDesktopStyleApplicationLifetime)app.ApplicationLifetime!).MainWindow = window;
+ return cts.Token;
}
+ }
- private static bool IsError(LauncherErrorCode errorCode)
+ private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ if (e.IsTerminating)
{
- return (int)errorCode < 0;
+ HandleException(e.ExceptionObject as Exception, CrashLocation.UnhandledException);
}
}
-}
+ private static void HandleException(Exception? exception, CrashLocation location)
+ {
+ if (exception is null) return;
+
+ // prevent multiple crash reports
+ if (Interlocked.CompareExchange(ref terminating, 1, 0) == 1) return;
+ var englishCulture = new CultureInfo("en-US");
+ Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = englishCulture;
+ var reportArgs = new CrashReportArgs
+ {
+ Exception = exception,
+ Location = location,
+ ThreadName = Thread.CurrentThread.Name
+ };
+ CrashReport(reportArgs);
+ }
+
+ #endregion // Crash
+}
diff --git a/sources/launcher/Stride.Launcher/LauncherArguments.cs b/sources/launcher/Stride.Launcher/LauncherArguments.cs
index 4741058206..0cc760f1a6 100644
--- a/sources/launcher/Stride.Launcher/LauncherArguments.cs
+++ b/sources/launcher/Stride.Launcher/LauncherArguments.cs
@@ -1,26 +1,27 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System.Collections.Generic;
-namespace Stride.LauncherApp
+
+namespace Stride.Launcher;
+
+///
+/// A structure representing the arguments passed to the launcher process.
+///
+internal struct LauncherArguments
{
///
- /// A structure representing the arguments passed to the launcher process.
+ /// An enum representing the type of action this process should perform.
///
- internal struct LauncherArguments
+ public enum ActionType
{
- ///
- /// An enum representing the type of action this process should perform.
- ///
- public enum ActionType
- {
- Run,
- Uninstall,
- }
-
- ///
- /// The list of actions this process should perform.
- ///
- public List Actions;
+ Run,
+ Uninstall,
}
+
+ ///
+ /// The list of actions this process should perform.
+ ///
+ public List Actions;
+
+ public string[] Args;
}
diff --git a/sources/launcher/Stride.Launcher/LauncherErrorCode.cs b/sources/launcher/Stride.Launcher/LauncherErrorCode.cs
index 1e6298797a..a56d1ce583 100644
--- a/sources/launcher/Stride.Launcher/LauncherErrorCode.cs
+++ b/sources/launcher/Stride.Launcher/LauncherErrorCode.cs
@@ -1,29 +1,28 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-namespace Stride.LauncherApp
+namespace Stride.Launcher;
+
+///
+/// An enum representing error codes returned by the launcher process.
+///
+public enum LauncherErrorCode
{
- ///
- /// An enum representing error codes returned by the launcher process.
- ///
- public enum LauncherErrorCode
- {
- Success = 0,
+ Success = 0,
- // Non-error values (positive)
- ServerAlreadyRunning = 1,
+ // Non-error values (positive)
+ ServerAlreadyRunning = 1,
- // RunServer errors: -1 to -100
- ErrorWhileRunningServer = -1, // We don't have a more accurate error for the moment.
- ErrorWhileInitializingServer = -2,
+ // RunServer errors: -1 to -100
+ ErrorWhileRunningServer = -1, // We don't have a more accurate error for the moment.
+ ErrorWhileInitializingServer = -2,
- // UpdateTargets errors: -101 to -200
- ErrorUpdatingTargetFiles = -101, // We don't have a more accurate error for the moment.
+ // UpdateTargets errors: -101 to -200
+ ErrorUpdatingTargetFiles = -101, // We don't have a more accurate error for the moment.
- // Uninstall errors: -201 to -300
- UninstallCancelled = -201,
- ErrorWhileUninstalling = -202, // We don't have a more accurate error for the moment.
+ // Uninstall errors: -201 to -300
+ UninstallCancelled = -201,
+ ErrorWhileUninstalling = -202, // We don't have a more accurate error for the moment.
- UnknownError = -10000
- }
+ UnknownError = -10000
}
diff --git a/sources/launcher/Stride.Launcher/LauncherInstance.cs b/sources/launcher/Stride.Launcher/LauncherInstance.cs
deleted file mode 100644
index 1344c36236..0000000000
--- a/sources/launcher/Stride.Launcher/LauncherInstance.cs
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Threading;
-using Stride.Core.Extensions;
-using Stride.Core.Packages;
-using Stride.Core.Presentation.Services;
-using Stride.Core.Presentation.View;
-using Stride.Core.Presentation.Windows;
-using Stride.LauncherApp.Views;
-
-namespace Stride.LauncherApp
-{
- ///
- /// A class that manages a launcher instance, which must be single per user. It manages to show and hide windows, and keep the services alive
- ///
- internal class LauncherInstance
- {
- private IDispatcherService dispatcher;
- private LauncherWindow launcherWindow;
- private NugetStore store;
- private App app;
-
- public LauncherErrorCode Run(bool shouldStartHidden)
- {
- dispatcher = new DispatcherService(Dispatcher.CurrentDispatcher);
-
- // Note: Initialize is responsible of displaying a message box in case of error
- if (!Initialize())
- return LauncherErrorCode.ErrorWhileInitializingServer;
-
- app = new App { ShutdownMode = ShutdownMode.OnExplicitShutdown };
- app.InitializeComponent();
-
- using (new WindowManager(Dispatcher.CurrentDispatcher))
- {
- dispatcher.InvokeTask(() => ApplicationEntryPoint(shouldStartHidden)).Forget();
- app.Run();
- }
-
- return LauncherErrorCode.Success;
- }
-
- internal void ShowMainWindow()
- {
- // This method can be invoked only from the dispatcher thread.
- dispatcher.EnsureAccess();
-
- if (launcherWindow == null)
- {
- // Create the window if we don't have it yet.
- launcherWindow = new LauncherWindow();
- launcherWindow.Initialize(store);
- launcherWindow.Closed += (s, e) => launcherWindow = null;
- }
- if (WindowManager.MainWindow == null)
- {
- // Show it if it's currently not visible
- WindowManager.ShowMainWindow(launcherWindow);
- }
- else
- {
- // Otherwise just activate it.
- if (launcherWindow.WindowState == WindowState.Minimized)
- {
- launcherWindow.WindowState = WindowState.Normal;
- }
- launcherWindow.Activate();
- }
-
- }
-
- internal void CloseMainWindow()
- {
- // This method can be invoked only from the dispatcher thread.
- dispatcher.EnsureAccess();
-
- launcherWindow.Close();
- }
-
- internal async void ForceExit()
- {
- await Shutdown();
- }
-
- ///
- /// Setup the Launcher's service interface to handle IPC communications.
- ///
- private bool Initialize()
- {
- // Setup the Nuget store
- store = Launcher.InitializeNugetStore();
-
- return true;
- }
-
- private async Task Shutdown()
- {
- // Close view elements first
- launcherWindow?.Close();
-
- // Yield so that tasks that were awaiting can complete and the server can gracefully terminate
- await Task.Yield();
-
- // Terminate the server and the app at last
- app.Shutdown();
- }
-
- private async Task ApplicationEntryPoint(bool shouldStartHidden)
- {
- var authenticated = await CheckAndPromptCredentials();
-
- if (!authenticated)
- Shutdown();
-
- if (!shouldStartHidden)
- ShowMainWindow();
- }
-
- ///
- /// Ask users for his/her credentials if no session is authenticated or has expired.
- ///
- /// true if session was validated, false otherwise.
- private async Task CheckAndPromptCredentials()
- {
- // This method can be invoked only from the dispatcher thread.
- dispatcher.EnsureAccess();
-
- // Return whether or not we're now successfully authenticated.
- return true;
- }
-
- private void RequestShowMainWindow()
- {
- dispatcher.EnsureAccess(false);
- dispatcher.Invoke(ShowMainWindow);
- }
-
- private void RequestCloseMainWindow()
- {
- dispatcher.EnsureAccess(false);
- dispatcher.Invoke(CloseMainWindow);
- }
-
- private bool RequestCheckAndPromptCredentials()
- {
- dispatcher.EnsureAccess(false);
- return dispatcher.InvokeTask(CheckAndPromptCredentials).Result;
- }
- }
-}
diff --git a/sources/launcher/Stride.Launcher/PackageFilterExtensions.cs b/sources/launcher/Stride.Launcher/PackageFilterExtensions.cs
index 5e5e8ceb14..547af57b1b 100644
--- a/sources/launcher/Stride.Launcher/PackageFilterExtensions.cs
+++ b/sources/launcher/Stride.Launcher/PackageFilterExtensions.cs
@@ -1,18 +1,18 @@
-using System.Collections.Generic;
-using System.Linq;
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
using Stride.Core;
using Stride.Core.Packages;
-namespace Stride.LauncherApp
+namespace Stride.Launcher;
+
+internal static class PackageFilterExtensions
{
- static class PackageFilterExtensions
+ public static IEnumerable FilterStrideMainPackages(this IEnumerable packages) where T : NugetPackage
{
- public static IEnumerable FilterStrideMainPackages(this IEnumerable packages) where T : NugetPackage
- {
- // Stride up to 3.0 package is Xenko, 3.x is Xenko.GameStudio, then Stride.GameStudio
- return packages.Where(x => (x.Id == "Xenko" && x.Version < new PackageVersion(3, 1, 0, 0))
- || (x.Id == "Xenko.GameStudio" && x.Version < new PackageVersion(4, 0, 0, 0))
- || (x.Id == "Stride.GameStudio"));
- }
+ // Stride up to 3.0 package is Xenko, 3.x is Xenko.GameStudio, then Stride.GameStudio
+ return packages.Where(x => (x.Id is Names.Xenko && x.Version < new PackageVersion(3, 1, 0, 0))
+ || (x.Id is GameStudioNames.Xenko && x.Version < new PackageVersion(4, 0, 0, 0))
+ || (x.Id is GameStudioNames.Stride or GameStudioNames.StrideAvalonia));
}
}
diff --git a/sources/launcher/Stride.Launcher/PrerequisitesValidator.cs b/sources/launcher/Stride.Launcher/PrerequisitesValidator.cs
deleted file mode 100644
index 06dcb59872..0000000000
--- a/sources/launcher/Stride.Launcher/PrerequisitesValidator.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Text;
-using System.Windows.Forms;
-using Microsoft.Win32;
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-namespace Stride.LauncherApp
-{
- internal static class PrerequisitesValidator
- {
- private const string LauncherPrerequisites = @"Prerequisites\launcher-prerequisites.exe";
-
- private static bool CheckDotNet4Version(int requiredVersion)
- {
- // Check for .NET v4 version
- using (var ndpKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"))
- {
- if (ndpKey == null)
- return false;
-
- int releaseKey = Convert.ToInt32(ndpKey.GetValue("Release"));
- if (releaseKey < requiredVersion)
- return false;
- }
-
- return true;
- }
-
- private static bool ValidateDotNet4Version(StringBuilder prerequisiteLog)
- {
- var result = true;
-
- // Check for .NET 4.7.2+
- // Note: it should now always be the case since renaming: Stride launcher is a separate forced setup to run, and it checks for 4.7.2.
- // Still keeping code for future framework updates
- if (!CheckDotNet4Version(461808))
- {
- prerequisiteLog.AppendLine("- .NET framework 4.7.2");
- result = false;
- }
-
- // Everything passed
- return result;
- }
-
- internal static void Validate(string[] args)
- {
- // Check prerequisites
- var prerequisiteLog = new StringBuilder();
- var prerequisitesFailedOnce = false;
- while (!ValidateDotNet4Version(prerequisiteLog))
- {
- prerequisitesFailedOnce = true;
-
- // Check if launcher prerequisite installer exists
- if (!File.Exists(LauncherPrerequisites))
- {
- MessageBox.Show($"Some prerequisites are missing, but no prerequisite installer was found!\n\n{prerequisiteLog}\n\nPlease install them manually or report the problem.", "Prerequisite error", MessageBoxButtons.OK);
- return;
- }
-
- // One of the prerequisite failed, launch the prerequisite installer
- var prerequisitesApproved = MessageBox.Show($"Some prerequisites are missing, do you want to install them?\n\n{prerequisiteLog}", "Install missing prerequisites?", MessageBoxButtons.OKCancel);
- if (prerequisitesApproved == DialogResult.Cancel)
- return;
-
- try
- {
- var prerequisitesInstallerProcess = Process.Start(LauncherPrerequisites);
- if (prerequisitesInstallerProcess == null)
- {
- MessageBox.Show($"There was an error running the prerequisite installer {LauncherPrerequisites}.", "Prerequisite error", MessageBoxButtons.OK);
- return;
- }
-
- prerequisitesInstallerProcess.WaitForExit();
- }
- catch
- {
- MessageBox.Show($"There was an error running the prerequisite installer {LauncherPrerequisites}.", "Prerequisite error", MessageBoxButtons.OK);
- return;
- }
- prerequisiteLog.Length = 0;
- }
-
- if (!prerequisitesFailedOnce)
- {
- return;
- }
- // If prerequisites failed at least once, we want to restart ourselves to run with proper .NET framework
- var exeLocation = Launcher.GetExecutablePath();
- if (File.Exists(exeLocation))
- {
- // Forward arguments
- for (int i = 0; i < args.Length; ++i)
- {
- // Quote arguments with spaces
- if (args[i].IndexOf(' ') != -1)
- args[i] = '\"' + args[i] + '\"';
- }
- var arguments = string.Join(" ", args);
-
- // Start process
- Process.Start(exeLocation, arguments);
- }
- return;
- }
- }
-}
diff --git a/sources/launcher/Stride.Launcher/Program.cs b/sources/launcher/Stride.Launcher/Program.cs
index a23e2c30de..5089a66ffb 100644
--- a/sources/launcher/Stride.Launcher/Program.cs
+++ b/sources/launcher/Stride.Launcher/Program.cs
@@ -1,16 +1,60 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-namespace Stride.LauncherApp
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Threading;
+
+namespace Stride.Launcher;
+
+internal sealed class Program
{
- static class Program
+ [STAThread]
+ private static int Main(string[] args)
{
- [STAThread]
- private static void Main(string[] args)
+ return (int)Launcher.Main(args);
+ }
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+
+ ///
+ /// Returns path of Launcher (we can't use Assembly.GetEntryAssembly().Location in .NET Core, especially with self-publish).
+ ///
+ ///
+ internal static string? GetExecutablePath() => Environment.ProcessPath;
+
+ internal static void RunNewApp(Func appMain, string[]? args = null)
+ where TApp : Application, new()
+ {
+ // Note: we need a new app because the main one may be already shutting down
+ var appBuilder = AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+
+ if (Application.Current is null)
{
- PrerequisitesValidator.Validate(args);
- Launcher.Main(args);
+ appBuilder = appBuilder
+ .SetupWithClassicDesktopLifetime(args ?? [], x => x.ShutdownMode = ShutdownMode.OnExplicitShutdown);
+ var app = appBuilder.Instance!;
+ app.Run(appMain((TApp)app));
+ }
+ else
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ // First hide the main window
+ ((IClassicDesktopStyleApplicationLifetime?)Application.Current.ApplicationLifetime)?.MainWindow?.Hide();
+
+ var app = appBuilder.Instance!;
+ app.Run(appMain((TApp)app));
+ });
}
}
}
diff --git a/sources/launcher/Stride.Launcher/Properties/AssemblyInfo.cs b/sources/launcher/Stride.Launcher/Properties/AssemblyInfo.cs
deleted file mode 100644
index 10aef5c897..0000000000
--- a/sources/launcher/Stride.Launcher/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System.Resources;
-using System.Runtime.InteropServices;
-
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-//[assembly: AssemblyFileVersion("1.0.0.0")]
-
-[assembly: NeutralResourcesLanguage("en-US")]
diff --git a/sources/launcher/Stride.Launcher/Properties/PublishProfiles/FolderProfile.pubxml b/sources/launcher/Stride.Launcher/Properties/PublishProfiles/FolderProfile.pubxml
index 19b6fe681a..75a12e28ec 100644
--- a/sources/launcher/Stride.Launcher/Properties/PublishProfiles/FolderProfile.pubxml
+++ b/sources/launcher/Stride.Launcher/Properties/PublishProfiles/FolderProfile.pubxml
@@ -1,4 +1,4 @@
-
+
@@ -8,12 +8,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
Any CPU
bin\Release\publish\
FileSystem
- net10.0-windows
true
- win-x64
true
false
true
false
-
\ No newline at end of file
+
diff --git a/sources/launcher/Stride.Launcher/Resources/Robot.jpg b/sources/launcher/Stride.Launcher/Resources/Robot.jpg
deleted file mode 100644
index e1044581cd..0000000000
--- a/sources/launcher/Stride.Launcher/Resources/Robot.jpg
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:157ed9116a53ecfe5738b6c2b695a3e6844ca0fa97cb840719d56ca7aafc6cce
-size 109714
diff --git a/sources/launcher/Stride.Launcher/Services/GameStudioSettings.cs b/sources/launcher/Stride.Launcher/Services/GameStudioSettings.cs
index d960721895..d17b4deafa 100644
--- a/sources/launcher/Stride.Launcher/Services/GameStudioSettings.cs
+++ b/sources/launcher/Stride.Launcher/Services/GameStudioSettings.cs
@@ -1,9 +1,6 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
+
using Stride.Core.Assets.Editor;
using Stride.Core.Extensions;
using Stride.Core.IO;
@@ -11,145 +8,144 @@
using Stride.Core.Settings;
using Stride.Core.Yaml;
-namespace Stride.LauncherApp.Services
+namespace Stride.Launcher.Services;
+
+public static class GameStudioSettings
{
- public static class GameStudioSettings
- {
- private static readonly SettingsProfile GameStudioProfile;
+ private static readonly SettingsProfile GameStudioProfile;
- private static readonly SettingsContainer InternalSettingsContainer = new SettingsContainer();
+ private static readonly SettingsContainer InternalSettingsContainer = new();
- private static readonly SettingsContainer GameStudioSettingsContainer = new SettingsContainer();
+ private static readonly SettingsContainer GameStudioSettingsContainer = new();
- private static readonly SettingsKey MostRecentlyUsedSessionsKey = new SettingsKey("Internal/MostRecentlyUsedSessions", InternalSettingsContainer, () => new MRUDictionary());
+ private static readonly SettingsKey MostRecentlyUsedSessionsKey = new("Internal/MostRecentlyUsedSessions", InternalSettingsContainer, () => new MRUDictionary());
- private static readonly SettingsKey StoreCrashEmail = new SettingsKey("Interface/StoreCrashEmail", GameStudioSettingsContainer, "");
+ private static readonly SettingsKey StoreCrashEmail = new("Interface/StoreCrashEmail", GameStudioSettingsContainer, "");
- private static readonly object LockObject = new object();
+ private static readonly object LockObject = new();
- private static readonly MostRecentlyUsedFileCollection MRU;
+ private static readonly MostRecentlyUsedFileCollection MRU;
- private static IReadOnlyCollection mostRecentlyUsed;
+ private static IReadOnlyCollection? mostRecentlyUsed;
- private static bool updating;
+ private static bool updating;
- static GameStudioSettings()
- {
- MRU = new MostRecentlyUsedFileCollection(() => InternalSettingsContainer.LoadSettingsProfile(GetLatestInternalConfigPath(), false, null, false), MostRecentlyUsedSessionsKey, () => InternalSettingsContainer.SaveSettingsProfile(InternalSettingsContainer.CurrentProfile, GetLatestInternalConfigPath()));
- MostRecentlyUsedSessionsKey.FallbackDeserializers.Add(LegacyMRUDeserializer);
- InternalSettingsContainer.LoadSettingsProfile(GetLatestInternalConfigPath(), true);
- InternalSettingsContainer.CurrentProfile.MonitorFileModification = true;
- InternalSettingsContainer.CurrentProfile.FileModified += (sender, e) => { GameStudioSettingsFileChanged(sender, e); };
- GameStudioProfile = GameStudioSettingsContainer.LoadSettingsProfile(GetLatestGameStudioConfigPath(), true);
- UpdateMostRecentlyUsed();
- }
+ static GameStudioSettings()
+ {
+ MRU = new MostRecentlyUsedFileCollection(() => InternalSettingsContainer.LoadSettingsProfile(GetLatestInternalConfigPath(), false, null, false), MostRecentlyUsedSessionsKey, () => InternalSettingsContainer.SaveSettingsProfile(InternalSettingsContainer.CurrentProfile, GetLatestInternalConfigPath()));
+ MostRecentlyUsedSessionsKey.FallbackDeserializers.Add(LegacyMRUDeserializer);
+ InternalSettingsContainer.LoadSettingsProfile(GetLatestInternalConfigPath(), true);
+ InternalSettingsContainer.CurrentProfile.MonitorFileModification = true;
+ InternalSettingsContainer.CurrentProfile.FileModified += GameStudioSettingsFileChanged;
+ GameStudioProfile = GameStudioSettingsContainer.LoadSettingsProfile(GetLatestGameStudioConfigPath(), true);
+ UpdateMostRecentlyUsed();
+ }
- public static event EventHandler RecentProjectsUpdated;
+ public static event EventHandler? RecentProjectsUpdated;
- public static string CrashReportEmail
+ public static string CrashReportEmail
+ {
+ get
{
- get
+ try
{
- try
- {
- lock (LockObject)
- {
- GameStudioSettingsContainer.ReloadSettingsProfile(GameStudioProfile);
- return StoreCrashEmail.GetValue();
- }
- }
- catch (Exception)
+ lock (LockObject)
{
- return "";
+ GameStudioSettingsContainer.ReloadSettingsProfile(GameStudioProfile);
+ return StoreCrashEmail.GetValue();
}
}
- set
+ catch (Exception)
{
- try
- {
- lock (LockObject)
- {
- GameStudioSettingsContainer.ReloadSettingsProfile(GameStudioProfile);
- StoreCrashEmail.SetValue(value);
- GameStudioSettingsContainer.SaveSettingsProfile(GameStudioProfile, GetLatestGameStudioConfigPath());
- }
- }
- catch (Exception e)
- {
- e.Ignore();
- }
+ return "";
}
}
-
- public static IReadOnlyCollection GetMostRecentlyUsed()
+ set
{
- List result;
- lock (LockObject)
+ try
{
- result = new List(mostRecentlyUsed);
+ lock (LockObject)
+ {
+ GameStudioSettingsContainer.ReloadSettingsProfile(GameStudioProfile);
+ StoreCrashEmail.SetValue(value);
+ GameStudioSettingsContainer.SaveSettingsProfile(GameStudioProfile, GetLatestGameStudioConfigPath());
+ }
}
- return result;
- }
-
- private static void GameStudioSettingsFileChanged(object sender, FileModifiedEventArgs e)
- {
- e.ReloadFile = true;
- UpdateMostRecentlyUsed();
- }
-
- public static void RemoveMostRecentlyUsed(UFile filePath, string strideVersion)
- {
- lock (LockObject)
+ catch (Exception e)
{
- MRU.RemoveFile(filePath, strideVersion);
- UpdateMostRecentlyUsed();
+ e.Ignore();
}
}
+ }
- private static void UpdateMostRecentlyUsed()
+ public static IReadOnlyCollection GetMostRecentlyUsed()
+ {
+ List result;
+ lock (LockObject)
{
- if (updating)
- return;
-
- lock (LockObject)
- {
- updating = true;
- MRU.LoadFromSettings();
- updating = false;
- mostRecentlyUsed = MRU.MostRecentlyUsedFiles.Select(x => x.FilePath).ToList();
- }
- RecentProjectsUpdated?.Invoke(null, EventArgs.Empty);
+ result = new(mostRecentlyUsed ?? Enumerable.Empty());
}
+ return result;
+ }
- private static string GetLatestInternalConfigPath()
- {
- return GetInternalConfigPaths().FirstOrDefault(File.Exists) ?? EditorPath.InternalConfigPath;
- }
+ private static void GameStudioSettingsFileChanged(object? sender, FileModifiedEventArgs e)
+ {
+ e.ReloadFile = true;
+ UpdateMostRecentlyUsed();
+ }
- private static string GetLatestGameStudioConfigPath()
+ public static void RemoveMostRecentlyUsed(UFile filePath, string strideVersion)
+ {
+ lock (LockObject)
{
- return GetGameStudioConfigPaths().FirstOrDefault(File.Exists) ?? EditorPath.EditorConfigPath;
+ MRU.RemoveFile(filePath, strideVersion);
+ UpdateMostRecentlyUsed();
}
+ }
- private static IEnumerable GetInternalConfigPaths()
- {
- yield return EditorPath.InternalConfigPath;
- }
+ private static void UpdateMostRecentlyUsed()
+ {
+ if (updating)
+ return;
- private static IEnumerable GetGameStudioConfigPaths()
+ lock (LockObject)
{
- yield return EditorPath.EditorConfigPath;
+ updating = true;
+ MRU.LoadFromSettings();
+ updating = false;
+ mostRecentlyUsed = MRU.MostRecentlyUsedFiles.Select(x => x.FilePath).ToList();
}
+ RecentProjectsUpdated?.Invoke(null, EventArgs.Empty);
+ }
+
+ private static string GetLatestInternalConfigPath()
+ {
+ return GetInternalConfigPaths().FirstOrDefault(File.Exists) ?? EditorPath.InternalConfigPath;
+ }
+
+ private static string GetLatestGameStudioConfigPath()
+ {
+ return GetGameStudioConfigPaths().FirstOrDefault(File.Exists) ?? EditorPath.EditorConfigPath;
+ }
+
+ private static IEnumerable GetInternalConfigPaths()
+ {
+ yield return EditorPath.InternalConfigPath;
+ }
+
+ private static IEnumerable GetGameStudioConfigPaths()
+ {
+ yield return EditorPath.EditorConfigPath;
+ }
- private static object LegacyMRUDeserializer(EventReader eventReader)
+ private static object LegacyMRUDeserializer(EventReader eventReader)
+ {
+ const string legacyVersion = "1.3";
+ var mru = (List)SettingsYamlSerializer.Default.Deserialize(eventReader, typeof(List));
+ var initialTimestamp = DateTime.UtcNow.Ticks;
+ return new Dictionary>
{
- const string legacyVersion = "1.3";
- var mru = (List)SettingsYamlSerializer.Default.Deserialize(eventReader, typeof(List));
- var initialTimestamp = DateTime.UtcNow.Ticks;
- return new Dictionary>
- {
- { legacyVersion, mru.Select(x => new MostRecentlyUsedFile(x) { Timestamp = initialTimestamp-- }).ToList() }
- };
- }
+ { legacyVersion, mru.Select(x => new MostRecentlyUsedFile(x) { Timestamp = initialTimestamp-- }).ToList() }
+ };
}
}
diff --git a/sources/launcher/Stride.Launcher/Services/LauncherSettings.cs b/sources/launcher/Stride.Launcher/Services/LauncherSettings.cs
index 62ecdd313f..9b39e16497 100644
--- a/sources/launcher/Stride.Launcher/Services/LauncherSettings.cs
+++ b/sources/launcher/Stride.Launcher/Services/LauncherSettings.cs
@@ -1,63 +1,60 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
+
using Stride.Core.Assets.Editor;
using Stride.Core.IO;
using Stride.Core.Settings;
-namespace Stride.LauncherApp.Services
+namespace Stride.Launcher.Services;
+
+public static class LauncherSettings
{
- public static class LauncherSettings
+ private static readonly SettingsContainer SettingsContainer = new();
+
+ private static readonly SettingsKey CloseLauncherAutomaticallyKey = new("Internal/Launcher/CloseLauncherAutomatically", SettingsContainer, false);
+ private static readonly SettingsKey ActiveVersionKey = new("Internal/Launcher/ActiveVersion", SettingsContainer, "");
+ private static readonly SettingsKey PreferredFrameworkKey = new("Internal/Launcher/PreferredFramework", SettingsContainer, "net10.0");
+ private static readonly SettingsKey CurrentTabKey = new("Internal/Launcher/CurrentTabSessions", SettingsContainer, 0);
+ private static readonly SettingsKey> DeveloperVersionsKey = new("Internal/Launcher/DeveloperVersions", SettingsContainer, () => new List());
+
+ private static readonly string LauncherConfigPath = Path.Combine(EditorPath.UserDataPath, "LauncherSettings.conf");
+
+ static LauncherSettings()
+ {
+ SettingsContainer.LoadSettingsProfile(GetLatestLauncherConfigPath(), true);
+ CloseLauncherAutomatically = CloseLauncherAutomaticallyKey.GetValue();
+ ActiveVersion = ActiveVersionKey.GetValue();
+ PreferredFramework = PreferredFrameworkKey.GetValue();
+ CurrentTab = CurrentTabKey.GetValue();
+ DeveloperVersions = DeveloperVersionsKey.GetValue();
+ }
+
+ public static void Save()
+ {
+ CloseLauncherAutomaticallyKey.SetValue(CloseLauncherAutomatically);
+ ActiveVersionKey.SetValue(ActiveVersion);
+ PreferredFrameworkKey.SetValue(PreferredFramework);
+ CurrentTabKey.SetValue(CurrentTab);
+ SettingsContainer.SaveSettingsProfile(SettingsContainer.CurrentProfile, LauncherConfigPath);
+ }
+
+ public static IReadOnlyCollection DeveloperVersions { get; private set; }
+
+ public static bool CloseLauncherAutomatically { get; set; }
+
+ public static string ActiveVersion { get; set; }
+
+ public static string PreferredFramework { get; set; }
+
+ public static int CurrentTab { get; set; }
+
+ private static string GetLatestLauncherConfigPath()
+ {
+ return GetLauncherConfigPaths().FirstOrDefault(File.Exists) ?? LauncherConfigPath;
+ }
+
+ private static IEnumerable GetLauncherConfigPaths()
{
- private static readonly SettingsContainer SettingsContainer = new SettingsContainer();
-
- private static readonly SettingsKey CloseLauncherAutomaticallyKey = new SettingsKey("Internal/Launcher/CloseLauncherAutomatically", SettingsContainer, false);
- private static readonly SettingsKey ActiveVersionKey = new SettingsKey("Internal/Launcher/ActiveVersion", SettingsContainer, "");
- private static readonly SettingsKey PreferredFrameworkKey = new SettingsKey("Internal/Launcher/PreferredFramework", SettingsContainer, "net10.0");
- private static readonly SettingsKey CurrentTabKey = new SettingsKey("Internal/Launcher/CurrentTabSessions", SettingsContainer, 0);
- private static readonly SettingsKey> DeveloperVersionsKey = new SettingsKey>("Internal/Launcher/DeveloperVersions", SettingsContainer, () => new List());
-
- private static readonly string LauncherConfigPath = Path.Combine(EditorPath.UserDataPath, "LauncherSettings.conf");
-
- static LauncherSettings()
- {
- SettingsContainer.LoadSettingsProfile(GetLatestLauncherConfigPath(), true);
- CloseLauncherAutomatically = CloseLauncherAutomaticallyKey.GetValue();
- ActiveVersion = ActiveVersionKey.GetValue();
- PreferredFramework = PreferredFrameworkKey.GetValue();
- CurrentTab = CurrentTabKey.GetValue();
- DeveloperVersions = DeveloperVersionsKey.GetValue();
- }
-
- public static void Save()
- {
- CloseLauncherAutomaticallyKey.SetValue(CloseLauncherAutomatically);
- ActiveVersionKey.SetValue(ActiveVersion);
- PreferredFrameworkKey.SetValue(PreferredFramework);
- CurrentTabKey.SetValue(CurrentTab);
- SettingsContainer.SaveSettingsProfile(SettingsContainer.CurrentProfile, LauncherConfigPath);
- }
-
- public static IReadOnlyCollection DeveloperVersions { get; private set; }
-
- public static bool CloseLauncherAutomatically { get; set; }
-
- public static string ActiveVersion { get; set; }
-
- public static string PreferredFramework { get; set; }
-
- public static int CurrentTab { get; set; }
-
- private static string GetLatestLauncherConfigPath()
- {
- return GetLauncherConfigPaths().FirstOrDefault(File.Exists) ?? LauncherConfigPath;
- }
-
- private static IEnumerable GetLauncherConfigPaths()
- {
- yield return LauncherConfigPath;
- }
+ yield return LauncherConfigPath;
}
}
diff --git a/sources/launcher/Stride.Launcher/Services/MetricsHelper.cs b/sources/launcher/Stride.Launcher/Services/MetricsHelper.cs
deleted file mode 100644
index 3a52e74cc9..0000000000
--- a/sources/launcher/Stride.Launcher/Services/MetricsHelper.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using Stride.Core.Extensions;
-
-namespace Stride.LauncherApp.Services
-{
- internal static class MetricsHelper
- {
- public static void NotifyDownloadStarting(string packageName, string packageVersion) => NotifyDownloadEvent(packageName, packageVersion, "DownloadStarting");
-
- public static void NotifyDownloadFailed(string packageName, string packageVersion) => NotifyDownloadEvent(packageName, packageVersion, "DownloadFailed");
-
- public static void NotifyDownloadCompleted(string packageName, string packageVersion) => NotifyDownloadEvent(packageName, packageVersion, "DownloadCompleted");
-
- private static void NotifyDownloadEvent(string packageName, string packageVersion, string downloadEvent)
- {
- try
- {
- var downloadInfo = BuildMessage(DateTime.UtcNow, downloadEvent, packageName, packageVersion);
- Launcher.Metrics?.DownloadPackage(downloadInfo);
- }
- catch (Exception e)
- {
- e.Ignore();
- }
- }
-
- private static string BuildMessage(DateTime dateTime, string downloadEvent, string packageName, string packageVersion)
- {
- var timestamp = (long)(dateTime - new DateTime(1970, 1, 1)).TotalSeconds;
- return $"{Escape(packageName)}||{Escape(packageVersion)}||{downloadEvent}||{timestamp}";
- }
-
- private static string Escape(string s) => s.Replace("|", @"\|");
- }
-}
diff --git a/sources/launcher/Stride.Launcher/Services/SelfUpdater.cs b/sources/launcher/Stride.Launcher/Services/SelfUpdater.cs
index 5013edddd0..cf62fa8bdd 100644
--- a/sources/launcher/Stride.Launcher/Services/SelfUpdater.cs
+++ b/sources/launcher/Stride.Launcher/Services/SelfUpdater.cs
@@ -1,283 +1,279 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Collections.Generic;
+
using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
+using Avalonia;
+using Avalonia.Controls;
using Stride.Core;
using Stride.Core.Extensions;
using Stride.Core.Packages;
using Stride.Core.Presentation.Services;
using Stride.Core.Presentation.ViewModels;
-using Stride.LauncherApp.Resources;
-using Stride.LauncherApp.Views;
-using MessageBoxButton = Stride.Core.Presentation.Services.MessageBoxButton;
-using MessageBoxImage = Stride.Core.Presentation.Services.MessageBoxImage;
+using Stride.Launcher.Assets.Localization;
+
+namespace Stride.Launcher.Services;
-namespace Stride.LauncherApp.Services
+public static class SelfUpdater
{
- public static class SelfUpdater
- {
- public static readonly string Version;
- private static readonly HttpClient httpClient = new();
+ public static readonly string? Version;
- private static SelfUpdateWindow selfUpdateWindow;
+ private static readonly HttpClient httpClient = new();
+ private static SelfUpdateWindow? selfUpdateWindow;
- static SelfUpdater()
- {
- var assembly = Assembly.GetEntryAssembly();
- var assemblyInformationalVersion = assembly.GetCustomAttribute();
- Version = assemblyInformationalVersion.InformationalVersion;
- }
+ static SelfUpdater()
+ {
+ var assembly = Assembly.GetEntryAssembly();
+ var assemblyInformationalVersion = assembly?.GetCustomAttribute();
+ Version = assemblyInformationalVersion?.InformationalVersion;
+ }
- internal static Task SelfUpdate(IViewModelServiceProvider services, NugetStore store)
+ public static void RestartApplication()
+ {
+ var args = Environment.GetCommandLineArgs().ToList();
+ args.Add("/UpdateTargets");
+ if (Program.GetExecutablePath() is string exeLocation)
{
- return Task.Run(async () =>
+
+ var startInfo = new ProcessStartInfo(exeLocation)
{
- var dispatcher = services.Get();
- try
- {
- await UpdateLauncherFiles(dispatcher, services.Get(), store, CancellationToken.None);
- }
- catch (Exception)
- {
- dispatcher.Invoke(() => selfUpdateWindow?.ForceClose());
- throw;
- }
- });
+ Arguments = string.Join(" ", args.Skip(1)),
+ WorkingDirectory = Environment.CurrentDirectory,
+ UseShellExecute = true
+ };
+ // Release the mutex before starting the new process
+ Launcher.Mutex?.Dispose();
+ Process.Start(startInfo);
}
+ Environment.Exit(0);
+ }
- private static async Task UpdateLauncherFiles(IDispatcherService dispatcher, IDialogService dialogService, NugetStore store, CancellationToken cancellationToken)
+ internal static Task SelfUpdate(IViewModelServiceProvider services, NugetStore store)
+ {
+ return Task.Run(async () =>
{
- var version = new PackageVersion(Version);
- var productAttribute = (typeof(SelfUpdater).Assembly).GetCustomAttribute();
- var packageId = productAttribute.Product;
- var packages = (await store.GetUpdates(new PackageName(packageId, version), true, true, cancellationToken)).OrderBy(x => x.Version);
-
+ var dispatcher = services.Get();
try
{
- // First, check if there is a package forcing us to download new installer
- const string ReinstallUrlPattern = @"force-reinstall:\s*(\S+)\s*(\S+)";
- var reinstallPackage = packages.LastOrDefault(x => x.Version > version && Regex.IsMatch(x.Description, ReinstallUrlPattern));
- if (reinstallPackage != null)
- {
- var regexMatch = Regex.Match(reinstallPackage.Description, ReinstallUrlPattern);
- var minimumVersion = PackageVersion.Parse(regexMatch.Groups[1].Value);
- if (version < minimumVersion)
- {
- var installerDownloadUrl = regexMatch.Groups[2].Value;
- await DownloadAndInstallNewVersion(dispatcher, dialogService, installerDownloadUrl);
- return;
- }
- }
+ await UpdateLauncherFiles(dispatcher, services.Get(), store, CancellationToken.None);
}
- catch (Exception e)
+ catch (Exception)
{
- await dialogService.MessageBoxAsync(string.Format(Strings.NewVersionDownloadError, e.Message), MessageBoxButton.OK, MessageBoxImage.Error);
+ await dispatcher.InvokeAsync(() => selfUpdateWindow?.ForceClose());
+ throw;
}
+ });
+ }
- // If there is a mandatory intermediate upgrade, take it, otherwise update straight to latest version
- var package = (packages.FirstOrDefault(x => x.Version > version && x.Version.SpecialVersion == "req") ?? packages.LastOrDefault());
+ private static async Task DownloadAndInstallNewVersion(IDispatcherService dispatcher, IDialogService dialogService, string strideInstallerUrl)
+ {
+ try
+ {
+ // Display progress window
+ await dispatcher.InvokeAsync(() =>
+ {
+ selfUpdateWindow = new();
+ selfUpdateWindow.LockWindow();
+ if (Application.Current is App { MainWindow: Window window })
+ {
+ _ = selfUpdateWindow.ShowDialog(window); // we don't await on purpose here
+ }
+ selfUpdateWindow.Show();
+ });
- // Check to see if an update is needed
- if (package == null || version >= new PackageVersion(package.Version.Version, package.Version.SpecialVersion))
+ var strideInstaller = Path.Combine(Path.GetTempPath(), $"StrideSetup-{Guid.NewGuid()}.exe");
+ using (var response = await httpClient.GetAsync(strideInstallerUrl))
{
- return;
+ response.EnsureSuccessStatusCode();
+
+ await using var responseStream = await response.Content.ReadAsStreamAsync();
+ await using var fileStream = File.Create(strideInstaller);
+ await responseStream.CopyToAsync(fileStream);
}
- var windowCreated = new TaskCompletionSource();
- var mainWindow = dispatcher.Invoke(() => Application.Current.MainWindow as LauncherWindow);
- if (mainWindow == null)
- throw new ApplicationException("Update requested without a Launcher Window. Cannot continue!");
- dispatcher.InvokeAsync(() =>
+ var startInfo = new ProcessStartInfo(strideInstaller)
{
- selfUpdateWindow = new SelfUpdateWindow { Owner = mainWindow };
- windowCreated.SetResult(selfUpdateWindow);
- selfUpdateWindow.ShowDialog();
- }).Forget();
-
- var movedFiles = new List();
+ UseShellExecute = true
+ };
+ // Release the mutex before starting the new process
+ Launcher.Mutex?.Dispose();
+ Process.Start(startInfo);
+ Environment.Exit(0);
+ }
+ catch (Exception e)
+ {
+ await dispatcher.InvokeAsync(() =>
+ {
+ selfUpdateWindow?.ForceClose();
+ });
- // Download package
- var installedPackage = await store.InstallPackage(package.Id, package.Version, package.TargetFrameworks, null);
+ await dialogService.MessageBoxAsync(string.Format(Strings.NewVersionDownloadError, e.Message), MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
- // Copy files from tools\ to the current directory
- var inputFiles = installedPackage.GetFiles();
+ private static async Task UpdateLauncherFiles(IDispatcherService dispatcher, IDialogService dialogService, NugetStore store, CancellationToken cancellationToken)
+ {
- var window = windowCreated.Task.Result;
- dispatcher.Invoke(window.LockWindow);
+ var version = new PackageVersion(Version);
+ var productAttribute = (typeof(SelfUpdater).Assembly).GetCustomAttribute();
+ var packageId = productAttribute!.Product;
+ var packages = (await store.GetUpdates(new(packageId, version), true, true, cancellationToken)).OrderBy(x => x.Version);
- // TODO: We should get list of previous files from nuspec (store it as a resource and open it with NuGet API maybe?)
- // TODO: For now, we deal only with the App.config file since we won't be able to fix it afterward.
- var exeLocation = Launcher.GetExecutablePath();
- var exeDirectory = Path.GetDirectoryName(exeLocation);
- const string directoryRoot = "tools/"; // Important!: this is matching where files are store in the nuspec
- try
+ try
+ {
+ // First, check if there is a package forcing us to download new installer
+ const string reinstallUrlPattern = @"force-reinstall:\s*(\S+)\s*(\S+)";
+ var reinstallPackage = packages.LastOrDefault(x => x.Version > version && Regex.IsMatch(x.Description, reinstallUrlPattern));
+ if (reinstallPackage is not null)
{
- if (File.Exists(exeLocation))
- {
- Move(exeLocation, exeLocation + ".old");
- movedFiles.Add(exeLocation);
- }
- var configLocation = exeLocation + ".config";
- if (File.Exists(configLocation))
+ var regexMatch = Regex.Match(reinstallPackage.Description, reinstallUrlPattern);
+ var minimumVersion = PackageVersion.Parse(regexMatch.Groups[1].Value);
+ if (version < minimumVersion)
{
- Move(configLocation, configLocation + ".old");
- movedFiles.Add(configLocation);
+ var installerDownloadUrl = regexMatch.Groups[2].Value;
+ await DownloadAndInstallNewVersion(dispatcher, dialogService, installerDownloadUrl);
+ return;
}
- foreach (var file in inputFiles.Where(file => file.Path.StartsWith(directoryRoot) && !file.Path.EndsWith("/")))
- {
- var fileName = Path.Combine(exeDirectory, file.Path.Substring(directoryRoot.Length));
+ }
+ }
+ catch (Exception e)
+ {
+ await dialogService.MessageBoxAsync(string.Format(Strings.NewVersionDownloadError, e.Message), MessageBoxButton.OK, MessageBoxImage.Error);
+ }
- // Move previous files to .old
- if (File.Exists(fileName))
- {
- Move(fileName, fileName + ".old");
- movedFiles.Add(fileName);
- }
+ // If there is a mandatory intermediate upgrade, take it, otherwise update straight to latest version
+ var package = (packages.FirstOrDefault(x => x.Version > version && x.Version.SpecialVersion == "req") ?? packages.LastOrDefault());
- // Update the file
- UpdateFile(fileName, file);
- }
- }
- catch (Exception)
+ // Check to see if an update is needed
+ if (package is null || version >= new PackageVersion(package.Version.Version, package.Version.SpecialVersion))
+ {
+ return;
+ }
+
+ // Display progress window
+ await dispatcher.InvokeAsync(() =>
+ {
+ selfUpdateWindow = new();
+ selfUpdateWindow.LockWindow();
+ if (Application.Current is App { MainWindow: Window window })
{
- // Revert all olds files if a file didn't work well
- foreach (var oldFile in movedFiles)
- {
- Move(oldFile + ".old", oldFile);
- }
- throw;
+ _ = selfUpdateWindow.ShowDialog(window); // we don't await on purpose here
}
-
-
- // Remove .old files
- foreach (var oldFile in movedFiles)
+ else
{
- try
- {
- var renamedPath = oldFile + ".old";
-
- if (File.Exists(renamedPath))
- {
- File.Delete(renamedPath);
- }
- }
- catch (Exception)
- {
- // All the files have been replaced, we let it go even if we cannot remove all the old files.
- }
+ throw new ApplicationException("Update requested without a Launcher Window. Cannot continue!");
}
+ }, cancellationToken);
- // Clean cache from files obtain via package.GetFiles above.
- store.PurgeCache();
+ var movedFiles = new List();
- dispatcher.Invoke(RestartApplication);
- }
+ // Download package
+ var installedPackage = await store.InstallPackage(package.Id, package.Version, package.TargetFrameworks, null);
+
+ // Copy files from tools\ to the current directory
+ var inputFiles = installedPackage.GetFiles();
- private static void Move(string oldPath, string newPath)
+ // TODO: We should get list of previous files from nuspec (store it as a resource and open it with NuGet API maybe?)
+ // TODO: For now, we deal only with the App.config file since we won't be able to fix it afterward.
+ var exeLocation = Program.GetExecutablePath();
+ var exeDirectory = Path.GetDirectoryName(exeLocation)!;
+ const string directoryRoot = "tools/"; // Important!: this is matching where files are store in the nuspec
+ try
{
- EnsureDirectory(newPath);
- try
+ if (File.Exists(exeLocation))
{
- if (File.Exists(newPath))
+ Move(exeLocation, exeLocation + ".old");
+ movedFiles.Add(exeLocation);
+ }
+ var configLocation = exeLocation + ".config";
+ if (File.Exists(configLocation))
+ {
+ Move(configLocation, configLocation + ".old");
+ movedFiles.Add(configLocation);
+ }
+ foreach (var file in inputFiles.Where(file => file.Path.StartsWith(directoryRoot) && !file.Path.EndsWith("/")))
+ {
+ var fileName = Path.Combine(exeDirectory, file.Path.Substring(directoryRoot.Length));
+
+ // Move previous files to .old
+ if (File.Exists(fileName))
{
- File.Delete(newPath);
+ Move(fileName, fileName + ".old");
+ movedFiles.Add(fileName);
}
+
+ // Update the file
+ UpdateFile(fileName, file);
}
- catch (FileNotFoundException)
+ }
+ catch (Exception)
+ {
+ // Revert all olds files if a file didn't work well
+ foreach (var oldFile in movedFiles)
{
-
+ Move(oldFile + ".old", oldFile);
}
-
- File.Move(oldPath, newPath);
+ throw;
}
- internal static async Task DownloadAndInstallNewVersion(IDispatcherService dispatcher, IDialogService dialogService, string strideInstallerUrl)
+ // Remove .old files
+ foreach (var oldFile in movedFiles)
{
try
{
- // Diplay progress window
- var mainWindow = dispatcher.Invoke(() => Application.Current.MainWindow as LauncherWindow);
- dispatcher.InvokeAsync(() =>
- {
- selfUpdateWindow = new SelfUpdateWindow { Owner = mainWindow };
- selfUpdateWindow.LockWindow();
- selfUpdateWindow.ShowDialog();
- }).Forget();
-
+ var renamedPath = oldFile + ".old";
- var strideInstaller = Path.Combine(Path.GetTempPath(), $"StrideSetup-{Guid.NewGuid()}.exe");
- using (var response = await httpClient.GetAsync(strideInstallerUrl))
+ if (File.Exists(renamedPath))
{
- response.EnsureSuccessStatusCode();
-
- await using var responseStream = await response.Content.ReadAsStreamAsync();
- await using var fileStream = File.Create(strideInstaller);
- responseStream.CopyTo(fileStream);
+ File.Delete(renamedPath);
}
-
- var startInfo = new ProcessStartInfo(strideInstaller)
- {
- UseShellExecute = true
- };
- // Release the mutex before starting the new process
- Launcher.Mutex.Dispose();
-
- Process.Start(startInfo);
-
- Environment.Exit(0);
}
- catch (Exception e)
+ catch (Exception)
{
- await dispatcher.InvokeAsync(() =>
- {
- selfUpdateWindow?.ForceClose();
- });
-
- await dialogService.MessageBoxAsync(string.Format(Strings.NewVersionDownloadError, e.Message), MessageBoxButton.OK, MessageBoxImage.Error);
+ // All the files have been replaced, we let it go even if we cannot remove all the old files.
}
}
- private static void EnsureDirectory(string filePath)
+ // Clean cache from files obtain via package.GetFiles above.
+ store.PurgeCache();
+ // Restart
+ dispatcher.InvokeAsync(RestartApplication, cancellationToken).Forget();
+ return;
+
+ static void EnsureDirectory(string filePath)
{
// Create dest directory if it exists
var directory = Path.GetDirectoryName(filePath);
- if (directory != null && !Directory.Exists(directory))
+ if (directory is not null && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
}
- private static void UpdateFile(string newFilePath, PackageFile file)
+ static void Move(string oldPath, string newPath)
{
- EnsureDirectory(newFilePath);
- using Stream fromStream = file.GetStream(), toStream = File.Create(newFilePath);
- fromStream.CopyTo(toStream);
+ EnsureDirectory(newPath);
+ try
+ {
+ if (File.Exists(newPath))
+ {
+ File.Delete(newPath);
+ }
+ }
+ catch (FileNotFoundException)
+ {
+ }
+
+ File.Move(oldPath, newPath);
}
- public static void RestartApplication()
+ static void UpdateFile(string newFilePath, PackageFile file)
{
- var args = Environment.GetCommandLineArgs().ToList();
- args.Add("/UpdateTargets");
- var exeLocation = Launcher.GetExecutablePath();
- var startInfo = new ProcessStartInfo(exeLocation)
- {
- Arguments = string.Join(" ", args.Skip(1)),
- WorkingDirectory = Environment.CurrentDirectory,
- UseShellExecute = true
- };
- // Release the mutex before starting the new process
- Launcher.Mutex.Dispose();
- Process.Start(startInfo);
- Environment.Exit(0);
+ EnsureDirectory(newFilePath);
+ using var fromStream = file.GetStream();
+ using var toStream = File.Create(newFilePath);
+ fromStream.CopyTo(toStream);
}
}
}
diff --git a/sources/launcher/Stride.Launcher/Services/UninstallHelper.cs b/sources/launcher/Stride.Launcher/Services/UninstallHelper.cs
index 8fa6ef1808..9aaf0f2883 100644
--- a/sources/launcher/Stride.Launcher/Services/UninstallHelper.cs
+++ b/sources/launcher/Stride.Launcher/Services/UninstallHelper.cs
@@ -1,153 +1,149 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Collections.Generic;
+
using System.Diagnostics;
-using System.Linq;
using Stride.Core.Extensions;
using Stride.Core.Packages;
+using Stride.Core.Presentation.Avalonia.Windows;
using Stride.Core.Presentation.Services;
using Stride.Core.Presentation.ViewModels;
-namespace Stride.LauncherApp.Services
+namespace Stride.Launcher.Services;
+
+internal class UninstallHelper : IDisposable
{
- internal class UninstallHelper : IDisposable
- {
- private readonly IViewModelServiceProvider serviceProvider;
- private readonly NugetStore store;
+ private readonly NugetStore store;
- internal UninstallHelper(IViewModelServiceProvider serviceProvider, NugetStore store)
- {
- this.serviceProvider = serviceProvider;
- this.store = store;
- store.NugetPackageUninstalling += PackageUninstalling;
- }
+ internal UninstallHelper(IViewModelServiceProvider serviceProvider, NugetStore store)
+ {
+ this.store = store;
+ store.NugetPackageUninstalling += PackageUninstalling;
+ }
- public void Dispose()
- {
- store.NugetPackageUninstalling -= PackageUninstalling;
- }
+ public void Dispose()
+ {
+ store.NugetPackageUninstalling -= PackageUninstalling;
+ }
- ///
- /// Closes all processes that were started from the given directory or one of its subdirectory. If the process has a window,
- /// this method will spawn a dialog box to ask the user to terminate the process himself.
- ///
- /// An function that will display a message box with the given text and OK/Cancel buttons, and returns True if the user pressed OK or False if he pressed Cancel.
- /// The name of the program being uninstalled, used for displaying a dialog message.
- /// The path in which processes to terminate are located.
- /// True if all the processes were terminated, False if the user cancelled the operation.
- /// There is no guarantee that all processes will be killed at the end. An error might occurs when trying to close a process.
- public static bool CloseProcessesInPath(Func showMessage, string uninstallingProgramName, string path)
+ ///
+ /// Closes all processes that were started from the given directory or one of its subdirectory. If the process has a window,
+ /// this method will spawn a dialog box to ask the user to terminate the process himself.
+ ///
+ /// An function that will display a message box with the given text and OK/Cancel buttons, and returns True if the user pressed OK or False if he pressed Cancel.
+ /// The name of the program being uninstalled, used for displaying a dialog message.
+ /// The path in which processes to terminate are located.
+ /// True if all the processes were terminated, False if the user cancelled the operation.
+ /// There is no guarantee that all processes will be killed at the end. An error might occurs when trying to close a process.
+ public static async Task CloseProcessesInPathAsync(Func> showMessageAsync, string uninstallingProgramName, string path)
+ {
+ // Check processes
+ var processesWithWindow = new List>();
+ List processes;
+ do
{
- // Check processes
- var processesWithWindow = new List>();
- List processes;
- do
- {
- processes = CollectPackageProcesses(path);
-
- // Make sure all process with main window are closed
- processesWithWindow.Clear();
- foreach (var process in processes)
- {
- try
- {
- if (process.MainWindowHandle != IntPtr.Zero)
- {
- processesWithWindow.Add(Tuple.Create(process.MainModule.ModuleName, process));
- }
- }
- catch (Exception exception)
- {
- exception.Ignore();
- }
- }
-
- // There is still process with main window, inform user so that he can properly close them
- if (processesWithWindow.Count > 0)
- {
- var nl = Environment.NewLine;
- // Display error to user and block until he presses try again
- var runningProcesses = string.Join(nl, processesWithWindow.GroupBy(x => x.Item1).Select(x => $" - {x.Key} ({x.Count()} instance(s))"));
- var message = $"Can't uninstall {uninstallingProgramName} because processes are still running:{nl}{runningProcesses}{nl}{nl}Please close them and press OK to try again, or Cancel to stop.";
- var confirmResult = showMessage(message);
-
- if (!confirmResult)
- {
- // User pressed Cancel, no need to uninstall
- return false;
- }
- }
- } while (processesWithWindow.Count > 0);
+ processes = CollectPackageProcesses(path);
- // Kill all other processes (there should be no processes with main window left, so probably services/console apps)
+ // Make sure all process with main window are closed
+ processesWithWindow.Clear();
foreach (var process in processes)
{
try
{
- try
- {
- process.StandardInput.Close();
- }
- catch
+ if (process.MainWindowHandle != IntPtr.Zero)
{
- process.Kill();
+ processesWithWindow.Add(Tuple.Create(process.MainModule!.ModuleName, process));
}
}
catch (Exception exception)
{
- // Ignore weird errors (process gone, etc...)
exception.Ignore();
}
}
- return true;
- }
+ // There is still process with main window, inform user so that he can properly close them
+ if (processesWithWindow.Count > 0)
+ {
+ var nl = Environment.NewLine;
+ // Display error to user and block until he presses try again
+ var runningProcesses = string.Join(nl, processesWithWindow.GroupBy(x => x.Item1).Select(x => $" - {x.Key} ({x.Count()} instance(s))"));
+ var message = $"Can't uninstall {uninstallingProgramName} because processes are still running:{nl}{runningProcesses}{nl}{nl}Please close them and press OK to try again, or Cancel to stop.";
+ var confirmResult = await showMessageAsync(message);
- private static bool IsPathInside(string folder, string path)
- {
- // Can probably be improved (not sure how stable and unique path could be?)
- return (path.IndexOf(folder, StringComparison.OrdinalIgnoreCase) != -1);
- }
+ if (!confirmResult)
+ {
+ // User pressed Cancel, no need to uninstall
+ return false;
+ }
+ }
+ } while (processesWithWindow.Count > 0);
- private static List CollectPackageProcesses(string installPath)
+ // Kill all other processes (there should be no processes with main window left, so probably services/console apps)
+ foreach (var process in processes)
{
- var result = new List();
- foreach (var process in Process.GetProcesses())
+ try
{
try
{
- var filename = process.MainModule.FileName;
-
- // Check if filename is inside install path
- if (!IsPathInside(installPath, filename))
- continue;
-
- // Discard ourselves
- if (process.Id == Environment.ProcessId)
- continue;
-
- result.Add(process);
+ process.StandardInput.Close();
}
- catch (Exception exception)
+ catch
{
- // Many errors can happen when accessing process main module (permission, process killed, etc...)
- exception.Ignore();
+ process.Kill();
}
}
-
- return result;
+ catch (Exception exception)
+ {
+ // Ignore weird errors (process gone, etc...)
+ exception.Ignore();
+ }
}
- private bool DisplayMessage(string message)
- {
- var result = serviceProvider.Get().BlockingMessageBox(message, MessageBoxButton.OKCancel);
- return result != MessageBoxResult.Cancel;
- }
+ return true;
+ }
+
+ private static bool IsPathInside(string folder, string path)
+ {
+ // Can probably be improved (not sure how stable and unique path could be?)
+ return (path.IndexOf(folder, StringComparison.OrdinalIgnoreCase) != -1);
+ }
- private void PackageUninstalling(object sender, PackageOperationEventArgs e)
+ private static List CollectPackageProcesses(string installPath)
+ {
+ var result = new List();
+ foreach (var process in Process.GetProcesses())
{
- CloseProcessesInPath(DisplayMessage, e.Id, e.InstallPath);
+ try
+ {
+ var filename = process.MainModule!.FileName;
+
+ // Check if filename is inside install path
+ if (!IsPathInside(installPath, filename))
+ continue;
+
+ // Discard ourselves
+ if (process.Id == Environment.ProcessId)
+ continue;
+
+ result.Add(process);
+ }
+ catch (Exception exception)
+ {
+ // Many errors can happen when accessing process main module (permission, process killed, etc...)
+ exception.Ignore();
+ }
}
+
+ return result;
+ }
+
+ private static async Task DisplayMessageAsync(string message)
+ {
+ var result = await MessageBox.ShowAsync(Launcher.ApplicationName, message, IDialogService.GetButtons(MessageBoxButton.OKCancel));
+ return result != (int)MessageBoxResult.Cancel;
+ }
+
+ private static async void PackageUninstalling(object? sender, PackageOperationEventArgs e)
+ {
+ await CloseProcessesInPathAsync(DisplayMessageAsync, e.Id, e.InstallPath);
}
}
diff --git a/sources/launcher/Stride.Launcher/Stride.Launcher.csproj b/sources/launcher/Stride.Launcher/Stride.Launcher.csproj
index 6acdc430b5..b02dc392d3 100644
--- a/sources/launcher/Stride.Launcher/Stride.Launcher.csproj
+++ b/sources/launcher/Stride.Launcher/Stride.Launcher.csproj
@@ -1,135 +1,78 @@
-
+
WinExe
- net10.0-windows
- win-x64
+ Exe
+ net10.0
+ embedded
+ linux-x64;win-x64
false
false
- true
- true
- false
- enable
-
-
- AnyCPU
- bin\Debug\
- false
- TRACE;STRIDE_LAUNCHER
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE;STRIDE_LAUNCHER
-
-
- Resources\Launcher.ico
- Stride.LauncherApp
+ true
+ Assets\Launcher.ico
app.manifest
- Stride.LauncherApp.Program
+ Stride.Launcher.Program
+ $(DefineConstants);STRIDE_LAUNCHER
<_StrideLauncherNuSpecLines>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)Stride.Launcher.nuspec'))
$([System.Text.RegularExpressions.Regex]::Match($(_StrideLauncherNuSpecLines), `(.*)`).Groups[1].Value)
+ enable
+ latest
+ enable
+ true
+
-
-
-
-
+
+
+
+
+
+
-
+
+
+
-
-
+
+
-
- Packages\PackageSessionHelper.Solution.cs
-
-
- Packages\Package.Constants.cs
-
-
- Editor\EditorPath.cs
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
- PublicResXFileCodeGenerator
- Strings.Designer.cs
+
+
+
Designer
-
-
-
+ Strings.Designer.cs
PublicResXFileCodeGenerator
+
+
+
+ Designer
Urls.Designer.cs
+ PublicResXFileCodeGenerator
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
True
True
Strings.resx
-
+
True
True
Urls.resx
-
+
-
-
diff --git a/sources/launcher/Stride.Launcher/Stride.Launcher.nuspec b/sources/launcher/Stride.Launcher/Stride.Launcher.nuspec
index bde13d9441..92f2275c4f 100644
--- a/sources/launcher/Stride.Launcher/Stride.Launcher.nuspec
+++ b/sources/launcher/Stride.Launcher/Stride.Launcher.nuspec
@@ -2,7 +2,7 @@
Stride.Launcher
- 5.0.6
+ 6.0.1
Stride
Stride
MIT
diff --git a/sources/launcher/Stride.Launcher/ViewModels/AnnouncementViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/AnnouncementViewModel.cs
index 0f439fd6d2..4636541a7f 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/AnnouncementViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/AnnouncementViewModel.cs
@@ -1,79 +1,67 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.IO;
-using System.Linq;
+
using System.Reflection;
-using Stride.Core.Extensions;
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+namespace Stride.Launcher.ViewModels;
+
+public sealed class AnnouncementViewModel : DispatcherViewModel
{
- internal class AnnouncementViewModel : DispatcherViewModel
- {
- private readonly LauncherViewModel launcher;
- private readonly string announcementName;
- private bool validated = true;
- private bool dontShowAgain;
+ private readonly string announcementName;
+ private bool dontShowAgain;
+ private bool validated = true;
- public AnnouncementViewModel(LauncherViewModel launcher, string announcementName)
- : base(launcher.SafeArgument(nameof(launcher)).ServiceProvider)
+ public AnnouncementViewModel(IViewModelServiceProvider serviceProvider, string announcementName)
+ : base(serviceProvider)
+ {
+ this.announcementName = announcementName;
+ if (!MainViewModel.HasDoneTask(TaskName))
{
- this.launcher = launcher;
- this.announcementName = announcementName;
- if (!LauncherViewModel.HasDoneTask(TaskName))
- {
- MarkdownAnnouncement = Initialize(announcementName);
- }
- // We want to explicitely trigger the property change notification for the view storyboard
- Dispatcher.InvokeAsync(() => Validated = false);
- CloseAnnouncementCommand = new AnonymousCommand(ServiceProvider, CloseAnnouncement);
+ MarkdownAnnouncement = Initialize(announcementName);
}
- private void CloseAnnouncement()
- {
- Validated = true;
- if (DontShowAgain)
- {
- LauncherViewModel.SaveTaskAsDone(TaskName);
- }
- }
+ CloseAnnouncementCommand = new AnonymousCommand(ServiceProvider, CloseAnnouncement);
+ // We want to explicitely trigger the property change notification for the view storyboard
+ Validated = false;
+ }
- public string MarkdownAnnouncement { get; }
+ public bool DontShowAgain { get { return dontShowAgain; } set { SetValue(ref dontShowAgain, value); } }
- public bool Validated { get { return validated; } set { SetValue(ref validated, value); } }
+ public string? MarkdownAnnouncement { get; }
- public bool DontShowAgain { get { return dontShowAgain; } set { SetValue(ref dontShowAgain, value); } }
+ public bool Validated { get { return validated; } set { SetValue(ref validated, value); } }
- public ICommandBase CloseAnnouncementCommand { get; }
+ private string TaskName => "Announcement" + announcementName;
- private string TaskName => GetTaskName(announcementName);
+ public ICommandBase CloseAnnouncementCommand { get; }
- public static string GetTaskName(string announcementName)
+ private void CloseAnnouncement()
+ {
+ Validated = true;
+ if (DontShowAgain)
{
- return "Announcement" + announcementName;
+ MainViewModel.SaveTaskAsDone(TaskName);
}
+ }
- private static string Initialize(string announcementName)
+ private static string? Initialize(string announcementName)
+ {
+ try
{
- try
- {
- var executingAssembly = Assembly.GetExecutingAssembly();
- var path = Assembly.GetExecutingAssembly().GetManifestResourceNames().Single(x => x.EndsWith(announcementName + ".md"));
- using (var stream = executingAssembly.GetManifestResourceStream(path))
- {
- if (stream == null)
- return null;
-
- using var reader = new StreamReader(stream);
- return reader.ReadToEnd();
- }
- }
- catch (Exception)
- {
+ var executingAssembly = Assembly.GetExecutingAssembly();
+ var path = Assembly.GetExecutingAssembly().GetManifestResourceNames().Single(x => x.EndsWith(announcementName + ".md"));
+ using var stream = executingAssembly.GetManifestResourceStream(path);
+ if (stream is null)
return null;
- }
+
+ using var reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+ catch (Exception)
+ {
+ return null;
}
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/DocumentationPageViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/DocumentationPageViewModel.cs
index ab14c0b07f..d07c0b54df 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/DocumentationPageViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/DocumentationPageViewModel.cs
@@ -1,132 +1,131 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Collections.Generic;
+
using System.Diagnostics;
-using System.Net.Http;
using System.Text.RegularExpressions;
-using System.Threading.Tasks;
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.Services;
using Stride.Core.Presentation.ViewModels;
-using Stride.LauncherApp.Resources;
+using Stride.Launcher.Assets.Localization;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+public sealed partial class DocumentationPageViewModel : DispatcherViewModel
{
- internal class DocumentationPageViewModel : DispatcherViewModel
+ private static readonly Regex ParsingRegex = GetParsingRegex();
+ private static readonly HttpClient httpClient = new();
+ private const string DocPageScheme = "page:";
+ private const string PageUrlFormatString = "{0}{1}";
+
+ public DocumentationPageViewModel(IViewModelServiceProvider serviceProvider, string version)
+ : base(serviceProvider)
{
- private static readonly Regex ParsingRegex = new Regex(@"\{([^\{\}]+)\}\{([^\{\}]+)\}\{([^\{\}]+)\}");
- private static readonly HttpClient httpClient = new();
- private const string DocPageScheme = "page:";
- private const string PageUrlFormatString = "{0}{1}";
+ Version = version;
+ OpenUrlCommand = new AnonymousTaskCommand(ServiceProvider, OpenUrl);
+ }
- public DocumentationPageViewModel(IViewModelServiceProvider serviceProvider, string version)
- : base(serviceProvider)
+ private async Task OpenUrl()
+ {
+ try
{
- Version = version;
- OpenUrlCommand = new AnonymousTaskCommand(ServiceProvider, OpenUrl);
+ Process.Start(new ProcessStartInfo(Url) { UseShellExecute = true });
}
-
- private async Task OpenUrl()
+ catch (Exception)
{
- try
- {
- Process.Start(new ProcessStartInfo(Url) { UseShellExecute = true });
- }
- catch (Exception)
- {
- await ServiceProvider.Get().MessageBoxAsync(Strings.ErrorOpeningBrowser, MessageBoxButton.OK, MessageBoxImage.Error);
- }
+ await ServiceProvider.Get().MessageBoxAsync(Strings.ErrorOpeningBrowser, MessageBoxButton.OK, MessageBoxImage.Error);
}
+ }
+
+ ///
+ /// Gets the root url of the documentation that should be opened when the user want to open Stride help.
+ ///
+ public string DocumentationRootUrl => GetDocumentationRootUrl(Version);
+
+ ///
+ /// Gets the version related to this documentation page.
+ ///
+ public string Version { get; }
+
+ ///
+ /// Gets or sets the title of this documentation page.
+ ///
+ public string? Title { get; set; }
- ///
- /// Gets the root url of the documentation that should be opened when the user want to open Stride help.
- ///
- public string DocumentationRootUrl => GetDocumentationRootUrl(Version);
-
- ///
- /// Gets the version related to this documentation page.
- ///
- public string Version { get; }
-
- ///
- /// Gets or sets the title of this documentation page.
- ///
- public string Title { get; set; }
-
- ///
- /// Gets or sets the description of this documentation page.
- ///
- public string Description { get; set; }
-
- ///
- /// Gets or sets the url of this documentation page.
- ///
- public string Url { get; set; }
-
- ///
- /// Gets a command that will open the documentation page in the default web browser.
- ///
- public ICommandBase OpenUrlCommand { get; private set; }
-
- public static async Task> FetchGettingStartedPages(IViewModelServiceProvider serviceProvider, string version)
+ ///
+ /// Gets or sets the description of this documentation page.
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// Gets or sets the url of this documentation page.
+ ///
+ public string? Url { get; set; }
+
+ ///
+ /// Gets a command that will open the documentation page in the default web browser.
+ ///
+ public ICommandBase OpenUrlCommand { get; private set; }
+
+ public static async Task> FetchGettingStartedPages(IViewModelServiceProvider serviceProvider, string version)
+ {
+ var result = new List();
+ string urlData;
+ try
{
- var result = new List();
- string urlData;
- try
- {
- using var response = await httpClient.GetAsync(string.Format(Urls.GettingStarted, version));
- response.EnsureSuccessStatusCode();
- urlData = await response.Content.ReadAsStringAsync();
+ using var response = await httpClient.GetAsync(string.Format(Urls.GettingStarted, version));
+ response.EnsureSuccessStatusCode();
+ urlData = await response.Content.ReadAsStringAsync();
- if (urlData == null)
+ if (urlData is null)
+ {
+ return result;
+ }
+ var urls = urlData.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+ foreach (var url in urls)
+ {
+ var match = ParsingRegex.Match(url);
+ if (!match.Success || match.Groups.Count != 4)
{
- return result;
+ continue;
}
- var urls = urlData.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
- foreach (var url in urls)
+ var link = match.Groups[3].Value;
+ if (link.StartsWith(DocPageScheme))
{
- var match = ParsingRegex.Match(url);
- if (!match.Success || match.Groups.Count != 4)
- {
- continue;
- }
- var link = match.Groups[3].Value;
- if (link.StartsWith(DocPageScheme))
- {
- link = GetDocumentationPageUrl(version, link.Substring(DocPageScheme.Length));
- }
- var page = new DocumentationPageViewModel(serviceProvider, version)
- {
- Title = match.Groups[1].Value.Trim(),
- Description = match.Groups[2].Value.Trim(),
- Url = link.Trim()
- };
- result.Add(page);
+ link = GetDocumentationPageUrl(version, link.Substring(DocPageScheme.Length));
}
- return result;
- }
- catch (Exception)
- {
- result.Clear();
+ var page = new DocumentationPageViewModel(serviceProvider, version)
+ {
+ Title = match.Groups[1].Value.Trim(),
+ Description = match.Groups[2].Value.Trim(),
+ Url = link.Trim()
+ };
+ result.Add(page);
}
return result;
}
-
- ///
- /// Compute the url of a documentation page, given the page name.
- ///
- /// The version related to this documentation page.
- /// The name of the page.
- /// The complete url of the documentation page.
- private static string GetDocumentationPageUrl(string version, string pageName)
+ catch (Exception)
{
- return string.Format(PageUrlFormatString, GetDocumentationRootUrl(version), pageName);
+ result.Clear();
}
+ return result;
+ }
- private static string GetDocumentationRootUrl(string version)
- {
- return string.Format(Urls.Documentation, version);
- }
+ ///
+ /// Compute the url of a documentation page, given the page name.
+ ///
+ /// The version related to this documentation page.
+ /// The name of the page.
+ /// The complete url of the documentation page.
+ private static string GetDocumentationPageUrl(string version, string pageName)
+ {
+ return string.Format(PageUrlFormatString, GetDocumentationRootUrl(version), pageName);
}
+
+ private static string GetDocumentationRootUrl(string version)
+ {
+ return string.Format(Urls.Documentation, version);
+ }
+
+ [GeneratedRegex(@"\{([^\{\}]+)\}\{([^\{\}]+)\}\{([^\{\}]+)\}")]
+ private static partial Regex GetParsingRegex();
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/FrameworkConverter.cs b/sources/launcher/Stride.Launcher/ViewModels/FrameworkConverter.cs
index d1afa8bdae..7ca85fac1d 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/FrameworkConverter.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/FrameworkConverter.cs
@@ -1,30 +1,25 @@
-using System;
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
using System.Globalization;
using NuGet.Frameworks;
-using Stride.Core.Annotations;
-using Stride.Core.Presentation.ValueConverters;
+using Stride.Core.Presentation.Avalonia.Converters;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+public sealed class FrameworkConverter : OneWayValueConverter
{
- class FrameworkConverter : ValueConverterBase
+ public override object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
- public override object Convert(object value, [NotNull] Type targetType, object parameter, CultureInfo culture)
- {
- var frameworkFolder = (string)value;
-
- var framework = NuGetFramework.ParseFolder(frameworkFolder);
- if (framework.Framework == ".NETFramework")
- return $".NET {framework.Version.ToString(3)}";
- else if (framework.Framework == ".NETCoreApp")
- return $".NET Core {framework.Version.ToString(2)}";
+ var frameworkFolder = (string?)value ?? string.Empty;
- // fallback
- return $"{framework.Framework} {framework.Version.ToString(3)}";
- }
+ var framework = NuGetFramework.ParseFolder(frameworkFolder);
+ if (framework.Framework == ".NETFramework")
+ return $".NET {framework.Version.ToString(3)}";
+ else if (framework.Framework == ".NETCoreApp")
+ return $".NET Core {framework.Version.ToString(2)}";
- public override object ConvertBack(object value, [NotNull] Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
+ // fallback
+ return $"{framework.Framework} {framework.Version.ToString(3)}";
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/LauncherViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/LauncherViewModel.cs
deleted file mode 100644
index f76b9c36f1..0000000000
--- a/sources/launcher/Stride.Launcher/ViewModels/LauncherViewModel.cs
+++ /dev/null
@@ -1,682 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Win32;
-using Stride.Core.CodeEditorSupport.VisualStudio;
-using Stride.Core.Extensions;
-using Stride.Core.Packages;
-using Stride.Core.Presentation.Collections;
-using Stride.Core.Presentation.Commands;
-using Stride.Core.Presentation.Services;
-using Stride.Core.Presentation.ViewModels;
-using Stride.LauncherApp.Resources;
-using Stride.LauncherApp.Services;
-using Stride.Metrics;
-
-namespace Stride.LauncherApp.ViewModels
-{
- ///
- /// This class represents the root view model of the launcher.
- ///
- internal class LauncherViewModel : DispatcherViewModel, IPackagesLogger, IDisposable
- {
- private readonly NugetStore store;
- private readonly SortedObservableCollection strideVersions = new SortedObservableCollection();
- private readonly UninstallHelper uninstallHelper;
- private readonly object objectLock = new object();
- private ObservableList newsPages;
- private ReleaseNotesViewModel activeReleaseNotes;
- private StrideVersionViewModel activeVersion;
- private bool isOffline;
- private bool isSynchronizing = true;
- private string currentToolTip;
- private List<(DateTime Time, MessageLevel Level, string Message)> logMessages = new();
- private bool autoCloseLauncher = LauncherSettings.CloseLauncherAutomatically;
- private bool lastActiveVersionRestored;
- private AnnouncementViewModel announcement;
- private bool isVisible;
- private bool showBetaVersions;
-
- internal LauncherViewModel(IViewModelServiceProvider serviceProvider, NugetStore store)
- : base(serviceProvider)
- {
- DependentProperties.Add("ActiveVersion", new[] { "ActiveDocumentationPages" });
- this.store = store ?? throw new ArgumentNullException(nameof(store));
- store.Logger = this;
-
- DisplayReleaseAnnouncement();
-
- VsixPackage2019 = new VsixVersionViewModel(this, store, store.VsixPackageId, NugetStore.VsixSupportedVsVersion.VS2019);
- VsixPackage2022 = new VsixVersionViewModel(this, store, store.VsixPackageId, NugetStore.VsixSupportedVsVersion.VS2022AndNext);
- // Commands
- InstallLatestVersionCommand = new AnonymousTaskCommand(ServiceProvider, InstallLatestVersion) { IsEnabled = false };
- OpenUrlCommand = new AnonymousTaskCommand(ServiceProvider, OpenUrl);
- ReconnectCommand = new AnonymousTaskCommand(ServiceProvider, async () =>
- {
- // We are back online (or so we think)
- IsOffline = false;
- await FetchOnlineData();
- });
- StartStudioCommand = new AnonymousTaskCommand(ServiceProvider, StartStudio) { IsEnabled = false };
- CheckDeprecatedSourcesCommand = new AnonymousTaskCommand(ServiceProvider, async () =>
- {
- var settings = NuGet.Configuration.Settings.LoadDefaultSettings(null);
- if (NugetStore.CheckPackageSource(settings, "Stride"))
- {
- return;
- }
- // Add Stride package store (still used for Xenko up to 3.0)
- if (await ServiceProvider.Get().MessageBoxAsync(Strings.AskAddNugetDeprecatedSource, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
- {
- NugetStore.UpdatePackageSource(settings, "Stride", "https://packages.stride3d.net/nuget");
- settings.SaveToDisk();
-
- SelfUpdater.RestartApplication();
- }
- });
-
- foreach (var devVersion in LauncherSettings.DeveloperVersions)
- {
- var version = new StrideDevVersionViewModel(this, store, null, devVersion, false);
- strideVersions.Add(version);
- }
- FetchOnlineData().Forget();
- LoadRecentProjects();
- uninstallHelper = new UninstallHelper(serviceProvider, store);
- GameStudioSettings.RecentProjectsUpdated += (sender, e) => Dispatcher.InvokeAsync(LoadRecentProjects).Forget();
- }
-
- public void Dispose()
- {
- uninstallHelper.Dispose();
- }
-
- public static IntPtr WindowHandle { get; set; }
-
- public IEnumerable StrideVersions => strideVersions;
-
- public bool ShowBetaVersions { get { return showBetaVersions; } set { SetValue(ref showBetaVersions, value); } }
-
- public VsixVersionViewModel VsixPackage2019 { get; }
-
- public VsixVersionViewModel VsixPackage2022 { get; }
-
- public StrideVersionViewModel ActiveVersion { get { return activeVersion; } set { SetValue(ref activeVersion, value); Dispatcher.InvokeAsync(() => StartStudioCommand.IsEnabled = (value != null) && value.CanStart); } }
-
- public ObservableList RecentProjects { get; } = new ObservableList();
-
- public ObservableList NewsPages { get { return newsPages; } private set { SetValue(ref newsPages, value); } }
-
- public ReleaseNotesViewModel ActiveReleaseNotes { get { return activeReleaseNotes; } set { SetValue(ref activeReleaseNotes, value); } }
-
- public ObservableList ActiveDocumentationPages => ActiveVersion.Yield().Concat(StrideVersions).OfType().FirstOrDefault()?.DocumentationPages;
-
- public AnnouncementViewModel Announcement { get { return announcement; } set { SetValue(ref announcement, value); } }
-
- public bool IsOffline { get { return isOffline; } set { SetValue(ref isOffline, value); } }
-
- public bool IsSynchronizing { get { return isSynchronizing; } set { SetValue(ref isSynchronizing, value); } }
-
- public string CurrentToolTip { get { return currentToolTip; } set { SetValue(ref currentToolTip, value); } }
-
- public string LogMessages
- {
- get
- {
- lock (logMessages)
- {
- if (logMessages.Count == 0)
- return "Empty";
- return string.Join(Environment.NewLine, logMessages.Select(x => $"[{x.Time.ToString("HH:mm:ss")}] {x.Level}: {x.Message}"));
- }
- }
- }
-
- public bool AutoCloseLauncher { get { return autoCloseLauncher; } set { SetValue(ref autoCloseLauncher, value, () => LauncherSettings.CloseLauncherAutomatically = value); } }
-
- ///
- /// Gets or Sets the visibility status of this instance.
- ///
- public bool IsVisible { get { return isVisible; } set { SetValue(ref isVisible, value); } }
-
- public CommandBase InstallLatestVersionCommand { get; }
-
- public CommandBase OpenUrlCommand { get; }
-
- public CommandBase ReconnectCommand { get; }
-
- public CommandBase StartStudioCommand { get; }
-
- public CommandBase CheckDeprecatedSourcesCommand { get; }
-
- private async Task FetchOnlineData()
- {
- // We ensure that the self-updater task starts once the app is running because it might invoke dialogs.
- IsSynchronizing = true;
- await Task.Run(async () =>
- {
- await RetrieveLocalStrideVersions();
- await RunLockTask(async () =>
- {
- try
- {
- await SelfUpdater.SelfUpdate(ServiceProvider, store);
- }
- catch (Exception e)
- {
- var message = $@"**An error occurred while updating the launcher. If the problem persists, please reinstall this application.**
-### Log
-```
-{LogMessages}
-```
-
-### Exception
-```
-{e.FormatSummary(false).TrimEnd(Environment.NewLine.ToCharArray())}
-```";
- await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
- // We do not want our users to use the old launcher when a new one is available.
- if (e is not HttpRequestException) // Prevent launcher closing when the user does not have internet access
- Environment.Exit(1);
- }
- });
- // Run news task early so that it can run while we fetch package versions
- var newsTask = FetchNewsPages();
-
- await RetrieveServerStrideVersions();
- await VsixPackage2019.UpdateFromStore();
- await VsixPackage2022.UpdateFromStore();
- await CheckForFirstInstall();
-
- await newsTask;
- });
- IsSynchronizing = false;
- }
-
- internal void LoadRecentProjects()
- {
- lock (RecentProjects)
- {
- RecentProjects.Clear();
- foreach (var mruFile in GameStudioSettings.GetMostRecentlyUsed())
- {
- RecentProjects.Add(new RecentProjectViewModel(this, mruFile));
- }
- }
- }
-
- public async Task RetrieveAllStrideVersions()
- {
- Dispatcher.Invoke(() => IsSynchronizing = true);
- await RetrieveLocalStrideVersions();
- await RetrieveServerStrideVersions();
- Dispatcher.Invoke(() => IsSynchronizing = false);
- }
-
- private class ReferencedPackageEqualityComparer : IEqualityComparer
- {
- public static readonly ReferencedPackageEqualityComparer Instance = new ReferencedPackageEqualityComparer();
-
- private ReferencedPackageEqualityComparer() { }
-
- public bool Equals(NugetLocalPackage x, NugetLocalPackage y)
- => (ReferenceEquals(x, y)) || ((!ReferenceEquals(x, null)) && (!ReferenceEquals(y, null)) && (x.Id == y.Id) && (x.Version.ToString() == y.Version.ToString()));
-
- public int GetHashCode([DisallowNull] NugetLocalPackage obj)
- => (obj.Id.GetHashCode() ^ obj.Version.ToString().GetHashCode());
- }
-
- private HashSet referencedPackages = new(ReferencedPackageEqualityComparer.Instance);
-
- private async Task RemoveUnusedPackages(IEnumerable mainPackages)
- {
- var previousReferencedPackages = referencedPackages;
- referencedPackages = new HashSet(ReferencedPackageEqualityComparer.Instance);
- foreach (var mainPackage in mainPackages)
- {
- await FindReferencedPackages(mainPackage);
- }
- foreach (var package in previousReferencedPackages.Where(package => !referencedPackages.Contains(package)))
- {
- await store.UninstallPackage(package, null);
- }
- }
-
- private async Task FindReferencedPackages(NugetLocalPackage package)
- {
- foreach (var dependency in package.Dependencies)
- {
- string prefix = dependency.Item1.Split('.', 2)[0];
- if (prefix is not "Stride" and not "Xenko")
- {
- continue;
- }
- NugetLocalPackage dependencyPackage = store.FindLocalPackage(dependency.Item1, dependency.Item2);
- if (dependencyPackage == null || referencedPackages.Contains(dependencyPackage))
- {
- continue;
- }
- referencedPackages.Add(dependencyPackage);
- await FindReferencedPackages(dependencyPackage);
- }
- }
-
- public async Task RetrieveLocalStrideVersions()
- {
- List currentRecentProjects;
- lock (RecentProjects)
- {
- currentRecentProjects = new List(RecentProjects);
- }
- try
- {
- var localPackages = await RunLockTask(() => store.GetPackagesInstalled(store.MainPackageIds).FilterStrideMainPackages().OrderByDescending(p => p.Version).ToList());
- lock (objectLock)
- {
- // Try to remove unused Stride/Xenko packages after uninstall or update
- try
- {
- Task.WaitAll(RemoveUnusedPackages(localPackages));
- }
- catch (Exception e)
- {
- var message = $@"**Failed to remove unused NuGet package(s).**
-
-### Exception
-```
-{e.FormatSummary(false).TrimEnd(Environment.NewLine.ToCharArray())}
-```";
- Task.WaitAll(ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Warning));
- }
-
- // Retrieve all local packages
- var packages = localPackages.Where(p => !store.IsDevRedirectPackage(p)).GroupBy(p => $"{p.Version.Version.Major}.{p.Version.Version.Minor}", p => p);
- var updatedLocalPackages = new HashSet();
- foreach (var package in packages)
- {
- var localPackage = package.FirstOrDefault();
- if (localPackage != null)
- {
- // Find if we already have this package in our list
- int index = strideVersions.BinarySearch(Tuple.Create(localPackage.Version.Version.Major, localPackage.Version.Version.Minor));
- StrideStoreVersionViewModel version;
- if (index < 0)
- {
- // If not, add it
- version = new StrideStoreVersionViewModel(this, store, localPackage, localPackage.Id, localPackage.Version.Version.Major, localPackage.Version.Version.Minor);
- Dispatcher.Invoke(() => strideVersions.Add(version));
- }
- else
- {
- version = (StrideStoreVersionViewModel)strideVersions[index];
- }
- version.UpdateLocalPackage(localPackage, package);
- updatedLocalPackages.Add(version);
- }
- }
-
- // Update versions that are not installed locally anymore
- Dispatcher.Invoke(() =>
- {
- foreach (var strideUninstalledVersion in strideVersions.OfType().Where(x => !updatedLocalPackages.Contains(x)))
- strideUninstalledVersion.UpdateLocalPackage(null, Array.Empty());
- });
-
- // Update the active version if it is now invalid.
- if (ActiveVersion == null || !strideVersions.Contains(ActiveVersion) || !ActiveVersion.CanDelete)
- ActiveVersion = StrideVersions.FirstOrDefault(x => x.CanDelete);
-
- if (!lastActiveVersionRestored)
- {
- var restoredVersion = StrideVersions.FirstOrDefault(x => x.CanDelete && x.Name == LauncherSettings.ActiveVersion);
- if (restoredVersion != null)
- {
- ActiveVersion = restoredVersion;
- lastActiveVersionRestored = true;
- }
- }
- }
-
- var devPackages = localPackages.Where(store.IsDevRedirectPackage);
- Dispatcher.Invoke(() => strideVersions.RemoveWhere(x => x is StrideDevVersionViewModel));
- foreach (var package in devPackages)
- {
- try
- {
- var realPath = store.GetRealPath(package);
- var version = new StrideDevVersionViewModel(this, store, package, realPath, true);
- Dispatcher.Invoke(() => strideVersions.Add(version));
- }
- catch (Exception e)
- {
- await ServiceProvider.Get().MessageBoxAsync(string.Format(Strings.ErrorDevRedirect, e), MessageBoxButton.OK, MessageBoxImage.Information);
- }
- }
- }
- catch (Exception e)
- {
- // TODO: error
- e.Ignore();
- }
- finally
- {
- Dispatcher.Invoke(() =>
- {
- foreach (var project in currentRecentProjects)
- {
- // Manually discarding the possibility to upgrade from 1.0
- if (project.StrideVersionName == "1.0")
- continue;
-
- project.CompatibleVersions.Clear();
- foreach (var version in StrideVersions)
- {
- // We suppose all dev versions are compatible with any project.
- if (version is StrideDevVersionViewModel)
- project.CompatibleVersions.Add(version);
-
- if (version is StrideStoreVersionViewModel storeVersion && storeVersion.CanDelete)
- {
- // Discard the version that matches the recent project version
- if (project.StrideVersion == new Version(storeVersion.Version.Version.Major, storeVersion.Version.Version.Minor))
- continue;
-
- // Discard the versions that are anterior to the recent project version
- if (project.StrideVersion > storeVersion.Version.Version)
- continue;
-
- project.CompatibleVersions.Add(version);
- }
- }
- }
- });
- }
- }
-
- private async Task RetrieveServerStrideVersions()
- {
- try
- {
- var serverPackages = await RunLockTask(() => store
- .FindSourcePackages(store.MainPackageIds, CancellationToken.None).Result
- .FilterStrideMainPackages()
- .Where(p => !store.IsDevRedirectPackage(p))
- .OrderByDescending(p => p.Version)
- .ToList());
-
- // Check if we could connect to the server
- var wasOffline = IsOffline;
- IsOffline = serverPackages.Count == 0;
-
- // Inform the user if we just switched offline
- if (IsOffline && !wasOffline)
- {
- var message = $@"**{Strings.ErrorOfflineMode}**
-### Log
-```
-{LogMessages}
-```";
- await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Information);
- }
-
- // We are offline, let's stop here
- if (IsOffline)
- return;
-
- lock (objectLock)
- {
- // Retrieve all server packages (ignoring dev ones)
- var packages = serverPackages
- //.Where(x => !string.Equals(x.Source, Environment.ExpandEnvironmentVariables(store.DevSource), StringComparison.OrdinalIgnoreCase))
- .GroupBy(p => $"{p.Version.Version.Major}.{p.Version.Version.Minor}", p => p);
- foreach (var package in packages)
- {
- var serverPackage = package.FirstOrDefault();
- if (serverPackage != null)
- {
- // Find if we already have this package in our list
- int index = strideVersions.BinarySearch(Tuple.Create(serverPackage.Version.Version.Major, serverPackage.Version.Version.Minor));
- StrideStoreVersionViewModel version;
- if (index < 0)
- {
- // If not, add it
- version = new StrideStoreVersionViewModel(this, store, null, serverPackage.Id, serverPackage.Version.Version.Major, serverPackage.Version.Version.Minor);
- Dispatcher.Invoke(() => strideVersions.Add(version));
- }
- else
- {
- // If yes, update it and remove it from the list of old version
- version = (StrideStoreVersionViewModel)strideVersions[index];
- }
- version.UpdateServerPackage(serverPackage, package);
- }
- }
- }
- }
- catch (Exception e)
- {
- // TODO: error
- e.Ignore();
- }
- finally
- {
- Dispatcher.Invoke(() =>
- {
- // Allow to install the latest version if any version is found
- var latestVersion = strideVersions.FirstOrDefault();
- if (latestVersion != null)
- {
- // Latest version not installed and can be downloaded
- if (latestVersion.CanBeDownloaded)
- InstallLatestVersionCommand.IsEnabled = !latestVersion.CanDelete && latestVersion.CanBeDownloaded;
- }
-
- OnPropertyChanging(nameof(ActiveDocumentationPages));
- OnPropertyChanged(nameof(ActiveDocumentationPages));
- });
- }
- }
-
- public async Task CheckForFirstInstall()
- {
- const string prerequisitesRunTaskName = "PrerequisitesRun";
-
- if (!HasDoneTask(prerequisitesRunTaskName))
- {
- foreach (var version in StrideVersions.OfType().Where(x => x.CanDelete))
- {
- await version.RunPrerequisitesInstaller();
- }
- SaveTaskAsDone(prerequisitesRunTaskName);
- }
-
- bool firstInstall = StrideVersions.All(x => !x.CanDelete) && StrideVersions.Any(x => x.CanBeDownloaded);
-
- await Dispatcher.InvokeTask(async () =>
- {
- if (firstInstall)
- {
- var result = await ServiceProvider.Get().MessageBoxAsync(Strings.AskInstallVersion, MessageBoxButton.YesNo, MessageBoxImage.Question);
- if (result == MessageBoxResult.Yes)
- {
- var versionToInstall = StrideVersions.First(x => x.CanBeDownloaded);
- await versionToInstall.Download(true);
-
- // if VS2022+ is installed (version 17.x+)
- if (!VsixPackage2022.IsLatestVersionInstalled && VsixPackage2022.CanBeDownloaded && VisualStudioVersions.AvailableInstances.Any(ide => ide.InstallationVersion.Major >= 17))
- {
- result = await ServiceProvider.Get().MessageBoxAsync(string.Format(Strings.AskInstallVSIX, "2022"), MessageBoxButton.YesNo, MessageBoxImage.Question);
- if (result == MessageBoxResult.Yes)
- {
- await VsixPackage2022.ExecuteAction();
- }
- }
-
- // if VS2019 is installed (version 16.x)
- if (!VsixPackage2019.IsLatestVersionInstalled && VsixPackage2019.CanBeDownloaded && VisualStudioVersions.AvailableInstances.Any(ide => ide.InstallationVersion.Major == 16))
- {
- result = await ServiceProvider.Get().MessageBoxAsync(string.Format(Strings.AskInstallVSIX, "2019"), MessageBoxButton.YesNo, MessageBoxImage.Question);
- if (result == MessageBoxResult.Yes)
- {
- await VsixPackage2019.ExecuteAction();
- }
- }
- }
- }
- });
- }
-
- ///
- /// Execute action under the exclusive lock .
- ///
- /// Return type of action.
- /// Action to be executed.
- /// Result of executing .
- internal Task RunLockTask(Func action)
- {
- return Task.Run(() =>
- {
- lock (objectLock)
- {
- return action();
- }
- });
- }
-
- public Task StartStudio()
- {
- return StartStudio("");
- }
-
- public async Task StartStudio(string argument)
- {
- if (argument == null) throw new ArgumentNullException(nameof(argument));
- if (ActiveVersion == null)
- return;
-
- if (AutoCloseLauncher)
- {
- argument = $"/LauncherWindowHandle {WindowHandle} {argument}";
- }
-
- MetricsClient metricsForEditorBefore120 = null;
- try
- {
- Dispatcher.Invoke(() => StartStudioCommand.IsEnabled = false);
- var mainExecutable = ActiveVersion.LocateMainExecutable();
-
- // If version is older than 1.2.0, than we need to log the usage of older version
- if (ActiveVersion is StrideStoreVersionViewModel activeStoreVersion && activeStoreVersion.Version.Version < new Version(1, 2, 0, 0))
- {
- metricsForEditorBefore120 = new MetricsClient(CommonApps.StrideEditorAppId, versionOverride: activeStoreVersion.Version.ToString());
- }
-
- // We set the WorkingDirectory so that global.json is properly resolved
- Process.Start(new ProcessStartInfo(mainExecutable, argument) { WorkingDirectory = Path.GetDirectoryName(mainExecutable) } );
- }
- catch (Exception e)
- {
- var message = string.Format(Strings.ErrorStartingProcess, e.FormatSummary(true));
- await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
- }
- finally
- {
- metricsForEditorBefore120?.Dispose();
- }
- await Task.Delay(5000);
- Dispatcher.Invoke(() =>
- {
- StartStudioCommand.IsEnabled = ActiveVersion != null && ActiveVersion.CanStart;
- //Save settings because launcher maybe have not been closed
- LauncherSettings.ActiveVersion = ActiveVersion != null ? ActiveVersion.Name : "";
- LauncherSettings.Save();
- });
- }
-
- private async Task InstallLatestVersion()
- {
- var latestVersion = strideVersions.FirstOrDefault();
- // Should never happen
- if (latestVersion == null || !latestVersion.CanBeDownloaded)
- return;
-
- if (latestVersion.IsProcessing)
- {
- await ServiceProvider.Get().MessageBoxAsync(Strings.InstallAlreadyInProgress, MessageBoxButton.OK, MessageBoxImage.Information);
- InstallLatestVersionCommand.IsEnabled = false;
- }
-
- latestVersion.DownloadCommand.Execute();
- }
-
- private async Task OpenUrl(string url)
- {
- try
- {
- Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
- }
- // FIXME: catch only specific exceptions?
- catch (Exception)
- {
- await ServiceProvider.Get().MessageBoxAsync(Strings.ErrorOpeningBrowser, MessageBoxButton.OK, MessageBoxImage.Error);
- }
- }
-
- private async Task FetchNewsPages()
- {
- var pages = await NewsPageViewModel.FetchNewsPages(ServiceProvider, 30);
- var sortedPages = pages.OrderBy(x => x.Date).Reverse().ToList();
- Dispatcher.Invoke(() => NewsPages = new ObservableList(sortedPages));
- }
-
- public static bool HasDoneTask(string taskName)
- {
- var localMachine32 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
- using (var subkey = localMachine32.OpenSubKey(@"SOFTWARE\Stride\"))
- {
- if (subkey != null)
- {
- var value = (string)subkey.GetValue(taskName);
- return value != null && value.ToLowerInvariant() == "true";
- }
- }
- return false;
- }
-
- public static void SaveTaskAsDone(string taskName)
- {
- var localMachine32 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
- using (var subkey = localMachine32.CreateSubKey(@"SOFTWARE\Stride\"))
- {
- subkey?.SetValue(taskName, "True");
- }
- }
-
- private void DisplayReleaseAnnouncement()
- {
- }
-
- void IPackagesLogger.Log(MessageLevel level, string message)
- {
- lock (logMessages)
- {
- logMessages.Add((DateTime.Now, level, message));
- }
- }
-
- Task IPackagesLogger.LogAsync(MessageLevel level, string message)
- {
- ((IPackagesLogger)this).Log(level, message);
- return Task.CompletedTask;
- }
- }
-}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/MainViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000000..a0351c9b1a
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/ViewModels/MainViewModel.cs
@@ -0,0 +1,699 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Win32;
+using Stride.Core.CodeEditorSupport.VisualStudio;
+using Stride.Core.Extensions;
+using Stride.Core.Packages;
+using Stride.Core.Presentation.Collections;
+using Stride.Core.Presentation.Commands;
+using Stride.Core.Presentation.Services;
+using Stride.Core.Presentation.ViewModels;
+using Stride.Launcher.Assets.Localization;
+using Stride.Launcher.Services;
+
+namespace Stride.Launcher.ViewModels;
+
+///
+/// This class represents the root view model of the launcher.
+///
+public sealed class MainViewModel : DispatcherViewModel, IPackagesLogger, IDisposable
+{
+ private readonly NugetStore store;
+ private readonly SortedObservableCollection strideVersions = [];
+ private readonly UninstallHelper uninstallHelper;
+ private readonly object objectLock = new();
+ private ObservableList newsPages;
+ private ReleaseNotesViewModel activeReleaseNotes;
+ private StrideVersionViewModel? activeVersion;
+ private bool isOffline;
+ private bool isSynchronizing = true;
+ private string currentToolTip;
+ private readonly List<(DateTime Time, MessageLevel Level, string Message)> logMessages = [];
+ private bool autoCloseLauncher = LauncherSettings.CloseLauncherAutomatically;
+ private bool lastActiveVersionRestored;
+ private AnnouncementViewModel announcement;
+ private bool isVisible;
+ private bool showBetaVersions;
+
+ public MainViewModel(IViewModelServiceProvider serviceProvider)
+ : base(serviceProvider)
+ {
+ DependentProperties.Add("ActiveVersion", ["ActiveDocumentationPages"]);
+ store = Launcher.InitializeNugetStore();
+ store.Logger = this;
+
+ DisplayReleaseAnnouncement();
+
+ VsixPackage2019 = new(this, store, store.VsixPackageId, NugetStore.VsixSupportedVsVersion.VS2019);
+ VsixPackage2022 = new(this, store, store.VsixPackageId, NugetStore.VsixSupportedVsVersion.VS2022AndNext);
+ // Commands
+ InstallLatestVersionCommand = new AnonymousTaskCommand(ServiceProvider, InstallLatestVersion) { IsEnabled = false };
+ OpenUrlCommand = new AnonymousTaskCommand(ServiceProvider, OpenUrl);
+ ReconnectCommand = new AnonymousTaskCommand(ServiceProvider, async () =>
+ {
+ // We are back online (or so we think)
+ IsOffline = false;
+ await FetchOnlineData();
+ });
+ StartStudioCommand = new AnonymousTaskCommand(ServiceProvider, StartStudio) { IsEnabled = false };
+ CheckDeprecatedSourcesCommand = new AnonymousTaskCommand(ServiceProvider, async () =>
+ {
+ var settings = NuGet.Configuration.Settings.LoadDefaultSettings(null);
+ if (NugetStore.CheckPackageSource(settings, "Stride"))
+ {
+ return;
+ }
+ // Add Stride package store (still used for Xenko up to 3.0)
+ if (await ServiceProvider.Get().MessageBoxAsync(Strings.AskAddNugetDeprecatedSource, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
+ {
+ NugetStore.UpdatePackageSource(settings, "Stride", "https://packages.stride3d.net/nuget");
+ settings.SaveToDisk();
+
+ SelfUpdater.RestartApplication();
+ }
+ });
+
+ foreach (var devVersion in LauncherSettings.DeveloperVersions)
+ {
+ var version = new StrideDevVersionViewModel(this, store, null, devVersion, false);
+ strideVersions.Add(version);
+ }
+ FetchOnlineData().Forget();
+ LoadRecentProjects();
+ uninstallHelper = new(serviceProvider, store);
+ GameStudioSettings.RecentProjectsUpdated += (sender, e) => Dispatcher.InvokeAsync(LoadRecentProjects).Forget();
+ }
+
+ public void Dispose()
+ {
+ uninstallHelper.Dispose();
+ }
+
+ public static IntPtr WindowHandle { get; set; }
+
+ public IEnumerable StrideVersions => strideVersions;
+
+ public bool ShowBetaVersions { get { return showBetaVersions; } set { SetValue(ref showBetaVersions, value); } }
+
+ public VsixVersionViewModel VsixPackage2019 { get; }
+
+ public VsixVersionViewModel VsixPackage2022 { get; }
+
+ public StrideVersionViewModel? ActiveVersion
+ {
+ get { return activeVersion; }
+ set
+ {
+ if (SetValue(ref activeVersion, value))
+ {
+ Dispatcher.InvokeAsync(() => StartStudioCommand.IsEnabled = value?.CanStart ?? false);
+ }
+ }
+ }
+
+ public ObservableList RecentProjects { get; } = [];
+
+ public ObservableList NewsPages { get { return newsPages; } private set { SetValue(ref newsPages, value); } }
+
+ public ReleaseNotesViewModel ActiveReleaseNotes { get { return activeReleaseNotes; } set { SetValue(ref activeReleaseNotes, value); } }
+
+ public ObservableList ActiveDocumentationPages => ActiveVersion.Yield().Concat(StrideVersions).OfType().FirstOrDefault()?.DocumentationPages;
+
+ public AnnouncementViewModel Announcement { get { return announcement; } set { SetValue(ref announcement, value); } }
+
+ public bool IsOffline { get { return isOffline; } set { SetValue(ref isOffline, value); } }
+
+ public bool IsSynchronizing { get { return isSynchronizing; } set { SetValue(ref isSynchronizing, value); } }
+
+ public string CurrentToolTip { get { return currentToolTip; } set { SetValue(ref currentToolTip, value); } }
+
+ public string LogMessages
+ {
+ get
+ {
+ lock (logMessages)
+ {
+ if (logMessages.Count == 0)
+ return "Empty";
+ return string.Join(Environment.NewLine, logMessages.Select(x => $"[{x.Time:HH:mm:ss}] {x.Level}: {x.Message}"));
+ }
+ }
+ }
+
+ public bool AutoCloseLauncher { get { return autoCloseLauncher; } set { SetValue(ref autoCloseLauncher, value, () => LauncherSettings.CloseLauncherAutomatically = value); } }
+
+ ///
+ /// Gets or Sets the visibility status of this instance.
+ ///
+ public bool IsVisible { get { return isVisible; } set { SetValue(ref isVisible, value); } }
+
+ public CommandBase InstallLatestVersionCommand { get; }
+
+ public CommandBase OpenUrlCommand { get; }
+
+ public CommandBase ReconnectCommand { get; }
+
+ public CommandBase StartStudioCommand { get; }
+
+ public CommandBase CheckDeprecatedSourcesCommand { get; }
+
+ private async Task FetchOnlineData()
+ {
+ // We ensure that the self-updater task starts once the app is running because it might invoke dialogs.
+ IsSynchronizing = true;
+ await Task.Run(async () =>
+ {
+ await RetrieveLocalStrideVersions();
+ await RunLockTask(async () =>
+ {
+ try
+ {
+ await SelfUpdater.SelfUpdate(ServiceProvider, store);
+ }
+ catch (Exception e)
+ {
+ var message = $@"**An error occurred while updating the launcher. If the problem persists, please reinstall this application.**
+### Log
+```
+{LogMessages}
+```
+
+### Exception
+```
+{e.FormatSummary(false).TrimEnd(Environment.NewLine.ToCharArray())}
+```";
+ await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
+ // We do not want our users to use the old launcher when a new one is available.
+ if (e is not HttpRequestException) // Prevent launcher closing when the user does not have internet access
+ Environment.Exit(1);
+ }
+ });
+ // Run news task early so that it can run while we fetch package versions
+ var newsTask = FetchNewsPages();
+
+ await RetrieveServerStrideVersions();
+ await VsixPackage2019.UpdateFromStore();
+ await VsixPackage2022.UpdateFromStore();
+ await CheckForFirstInstall();
+
+ await newsTask;
+ });
+ IsSynchronizing = false;
+ }
+
+ internal void LoadRecentProjects()
+ {
+ lock (RecentProjects)
+ {
+ RecentProjects.Clear();
+ foreach (var mruFile in GameStudioSettings.GetMostRecentlyUsed())
+ {
+ RecentProjects.Add(new(this, mruFile));
+ }
+ }
+ }
+
+ public async Task RetrieveAllStrideVersions()
+ {
+ Dispatcher.Invoke(() => IsSynchronizing = true);
+ await RetrieveLocalStrideVersions();
+ await RetrieveServerStrideVersions();
+ Dispatcher.Invoke(() => IsSynchronizing = false);
+ }
+
+ private class ReferencedPackageEqualityComparer : IEqualityComparer
+ {
+ public static readonly ReferencedPackageEqualityComparer Instance = new();
+
+ private ReferencedPackageEqualityComparer() { }
+
+ public bool Equals(NugetLocalPackage x, NugetLocalPackage y)
+ => (ReferenceEquals(x, y)) || ((!ReferenceEquals(x, null)) && (!ReferenceEquals(y, null)) && (x.Id == y.Id) && (x.Version.ToString() == y.Version.ToString()));
+
+ public int GetHashCode([DisallowNull] NugetLocalPackage obj)
+ => (obj.Id.GetHashCode() ^ obj.Version.ToString().GetHashCode());
+ }
+
+ private HashSet referencedPackages = new(ReferencedPackageEqualityComparer.Instance);
+
+ private async Task RemoveUnusedPackages(IEnumerable mainPackages)
+ {
+ var previousReferencedPackages = referencedPackages;
+ referencedPackages = new(ReferencedPackageEqualityComparer.Instance);
+ foreach (var mainPackage in mainPackages)
+ {
+ await FindReferencedPackages(mainPackage);
+ }
+ foreach (var package in previousReferencedPackages.Where(package => !referencedPackages.Contains(package)))
+ {
+ await store.UninstallPackage(package, null);
+ }
+ }
+
+ private async Task FindReferencedPackages(NugetLocalPackage package)
+ {
+ foreach (var dependency in package.Dependencies)
+ {
+ string prefix = dependency.Item1.Split('.', 2)[0];
+ if (prefix is not "Stride" and not "Xenko")
+ {
+ continue;
+ }
+ NugetLocalPackage dependencyPackage = store.FindLocalPackage(dependency.Item1, dependency.Item2);
+ if (dependencyPackage is null || !referencedPackages.Add(dependencyPackage))
+ {
+ continue;
+ }
+
+ await FindReferencedPackages(dependencyPackage);
+ }
+ }
+
+ public async Task RetrieveLocalStrideVersions()
+ {
+ List currentRecentProjects;
+ lock (RecentProjects)
+ {
+ currentRecentProjects = new(RecentProjects);
+ }
+ try
+ {
+ var localPackages = await RunLockTask(() => store.GetPackagesInstalled(store.MainPackageIds).FilterStrideMainPackages().OrderByDescending(p => p.Version).ToList());
+ lock (objectLock)
+ {
+ // Try to remove unused Stride/Xenko packages after uninstall or update
+ try
+ {
+ Task.WaitAll(RemoveUnusedPackages(localPackages));
+ }
+ catch (Exception e)
+ {
+ var message = $@"**Failed to remove unused NuGet package(s).**
+
+### Exception
+```
+{e.FormatSummary(false).TrimEnd(Environment.NewLine.ToCharArray())}
+```";
+ Task.WaitAll(ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Warning));
+ }
+
+ // Retrieve all local packages
+ var packages = localPackages.Where(p => !store.IsDevRedirectPackage(p)).GroupBy(p => $"{p.Version.Version.Major}.{p.Version.Version.Minor}", p => p);
+ var updatedLocalPackages = new HashSet();
+ foreach (var package in packages)
+ {
+ var localPackage = package.FirstOrDefault();
+ if (localPackage is not null)
+ {
+ // Find if we already have this package in our list
+ int index = strideVersions.BinarySearch(Tuple.Create(localPackage.Version.Version.Major, localPackage.Version.Version.Minor));
+ StrideStoreVersionViewModel version;
+ if (index < 0)
+ {
+ // If not, add it
+ version = new(this, store, localPackage, localPackage.Id, localPackage.Version.Version.Major, localPackage.Version.Version.Minor);
+ Dispatcher.Invoke(() => strideVersions.Add(version));
+ }
+ else
+ {
+ version = (StrideStoreVersionViewModel)strideVersions[index];
+ }
+ version.UpdateLocalPackage(localPackage, package);
+ updatedLocalPackages.Add(version);
+ }
+ }
+
+ // Update versions that are not installed locally anymore
+ Dispatcher.Invoke(() =>
+ {
+ foreach (var strideUninstalledVersion in strideVersions.OfType().Where(x => !updatedLocalPackages.Contains(x)))
+ strideUninstalledVersion.UpdateLocalPackage(null, Array.Empty());
+ });
+
+ // Update the active version if it is now invalid.
+ if (ActiveVersion is null || !strideVersions.Contains(ActiveVersion) || !ActiveVersion.CanDelete)
+ ActiveVersion = StrideVersions.FirstOrDefault(x => x.CanDelete);
+
+ if (!lastActiveVersionRestored)
+ {
+ var restoredVersion = StrideVersions.FirstOrDefault(x => x.CanDelete && x.Name == LauncherSettings.ActiveVersion);
+ if (restoredVersion is not null)
+ {
+ ActiveVersion = restoredVersion;
+ lastActiveVersionRestored = true;
+ }
+ }
+ }
+
+ var devPackages = localPackages.Where(store.IsDevRedirectPackage);
+ Dispatcher.Invoke(() => strideVersions.RemoveWhere(x => x is StrideDevVersionViewModel));
+ foreach (var package in devPackages)
+ {
+ try
+ {
+ var realPath = store.GetRealPath(package);
+ var version = new StrideDevVersionViewModel(this, store, package, realPath, true);
+ await Dispatcher.InvokeAsync(() => strideVersions.Add(version));
+ }
+ catch (Exception e)
+ {
+ await ServiceProvider.Get().MessageBoxAsync(string.Format(Strings.ErrorDevRedirect, e), MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // TODO: error
+ e.Ignore();
+ }
+ finally
+ {
+ await Dispatcher.InvokeAsync(() =>
+ {
+ foreach (var project in currentRecentProjects)
+ {
+ // Manually discarding the possibility to upgrade from 1.0
+ if (project.StrideVersionName == "1.0")
+ continue;
+
+ project.CompatibleVersions.Clear();
+ foreach (var version in StrideVersions)
+ {
+ // We suppose all dev versions are compatible with any project.
+ if (version is StrideDevVersionViewModel)
+ project.CompatibleVersions.Add(version);
+
+ if (version is StrideStoreVersionViewModel { CanDelete: true } storeVersion)
+ {
+ // Discard the version that matches the recent project version
+ if (project.StrideVersion == new Version(storeVersion.Version.Version.Major, storeVersion.Version.Version.Minor))
+ continue;
+
+ // Discard the versions that are anterior to the recent project version
+ if (project.StrideVersion > storeVersion.Version.Version)
+ continue;
+
+ project.CompatibleVersions.Add(version);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ private async Task RetrieveServerStrideVersions()
+ {
+ try
+ {
+ var serverPackages = await RunLockTask(() => store
+ .FindSourcePackages(store.MainPackageIds, CancellationToken.None).Result
+ .FilterStrideMainPackages()
+ .Where(p => !store.IsDevRedirectPackage(p))
+ .OrderByDescending(p => p.Version)
+ .ToList());
+
+ // Check if we could connect to the server
+ var wasOffline = IsOffline;
+ IsOffline = serverPackages.Count == 0;
+
+ // Inform the user if we just switched offline
+ if (IsOffline && !wasOffline)
+ {
+ var message =
+ $"""
+ **{Strings.ErrorOfflineMode}**
+ ### Log
+ ```
+ {LogMessages}
+ ```
+ """;
+ await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+
+ // We are offline, let's stop here
+ if (IsOffline)
+ return;
+
+ lock (objectLock)
+ {
+ // Retrieve all server packages (ignoring dev ones)
+ var packages = serverPackages
+ //.Where(x => !string.Equals(x.Source, Environment.ExpandEnvironmentVariables(store.DevSource), StringComparison.OrdinalIgnoreCase))
+ .GroupBy(p => $"{p.Version.Version.Major}.{p.Version.Version.Minor}", p => p);
+ foreach (var package in packages)
+ {
+ var serverPackage = package.FirstOrDefault();
+ if (serverPackage is not null)
+ {
+ // Find if we already have this package in our list
+ int index = strideVersions.BinarySearch(Tuple.Create(serverPackage.Version.Version.Major, serverPackage.Version.Version.Minor));
+ StrideStoreVersionViewModel version;
+ if (index < 0)
+ {
+ // If not, add it
+ version = new(this, store, null, serverPackage.Id, serverPackage.Version.Version.Major, serverPackage.Version.Version.Minor);
+ Dispatcher.Invoke(() => strideVersions.Add(version));
+ }
+ else
+ {
+ // If yes, update it and remove it from the list of old version
+ version = (StrideStoreVersionViewModel)strideVersions[index];
+ }
+ version.UpdateServerPackage(serverPackage, package);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // TODO: error
+ e.Ignore();
+ }
+ finally
+ {
+ await Dispatcher.InvokeAsync(() =>
+ {
+ // Allow to install the latest version if any version is found
+ var latestVersion = strideVersions.FirstOrDefault();
+ if (latestVersion is not null)
+ {
+ // Latest version not installed and can be downloaded
+ if (latestVersion.CanBeDownloaded)
+ InstallLatestVersionCommand.IsEnabled = latestVersion is { CanDelete: false, CanBeDownloaded: true };
+ }
+
+ OnPropertyChanging(nameof(ActiveDocumentationPages));
+ OnPropertyChanged(nameof(ActiveDocumentationPages));
+ });
+ }
+ }
+
+ public async Task CheckForFirstInstall()
+ {
+ const string prerequisitesRunTaskName = "PrerequisitesRun";
+
+ if (!HasDoneTask(prerequisitesRunTaskName))
+ {
+ foreach (var version in StrideVersions.OfType().Where(x => x.CanDelete))
+ {
+ await version.RunPrerequisitesInstaller();
+ }
+ SaveTaskAsDone(prerequisitesRunTaskName);
+ }
+
+ bool firstInstall = StrideVersions.All(x => !x.CanDelete) && StrideVersions.Any(x => x.CanBeDownloaded);
+
+ await Dispatcher.InvokeTask(async () =>
+ {
+ if (firstInstall)
+ {
+ var result = await ServiceProvider.Get().MessageBoxAsync(Strings.AskInstallVersion, MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ var versionToInstall = StrideVersions.First(x => x.CanBeDownloaded);
+ await versionToInstall.Download(true);
+
+ // if VS2022+ is installed (version 17.x+)
+ if (VsixPackage2022 is { IsLatestVersionInstalled: false, CanBeDownloaded: true } && VisualStudioVersions.AvailableInstances.Any(ide => ide.InstallationVersion.Major >= 17))
+ {
+ result = await ServiceProvider.Get().MessageBoxAsync(string.Format(Strings.AskInstallVSIX, "2022"), MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ await VsixPackage2022.ExecuteAction();
+ }
+ }
+
+ // if VS2019 is installed (version 16.x)
+ if (VsixPackage2019 is { IsLatestVersionInstalled: false, CanBeDownloaded: true } && VisualStudioVersions.AvailableInstances.Any(ide => ide.InstallationVersion.Major == 16))
+ {
+ result = await ServiceProvider.Get().MessageBoxAsync(string.Format(Strings.AskInstallVSIX, "2019"), MessageBoxButton.YesNo, MessageBoxImage.Question);
+ if (result == MessageBoxResult.Yes)
+ {
+ await VsixPackage2019.ExecuteAction();
+ }
+ }
+ }
+ }
+ });
+ }
+
+ ///
+ /// Execute action under the exclusive lock .
+ ///
+ /// Return type of action.
+ /// Action to be executed.
+ /// Result of executing .
+ internal Task RunLockTask(Func action)
+ {
+ return Task.Run(() =>
+ {
+ lock (objectLock)
+ {
+ return action();
+ }
+ });
+ }
+
+ public Task StartStudio()
+ {
+ return StartStudio("");
+ }
+
+ public async Task StartStudio(string argument)
+ {
+ ArgumentNullException.ThrowIfNull(argument);
+
+ if (ActiveVersion is null)
+ return;
+
+ if (AutoCloseLauncher)
+ {
+ argument = $"/LauncherWindowHandle {WindowHandle} {argument}";
+ }
+
+ try
+ {
+ Dispatcher.Invoke(() => StartStudioCommand.IsEnabled = false);
+ var mainExecutable = ActiveVersion.LocateMainExecutable();
+
+ // We set the WorkingDirectory so that global.json is properly resolved
+ switch (Path.GetExtension(mainExecutable))
+ {
+ case ".dll":
+ argument = $"{mainExecutable} {argument}";
+ Process.Start(new ProcessStartInfo("dotnet", argument)
+ {
+ WorkingDirectory = Path.GetDirectoryName(mainExecutable)
+ });
+ break;
+
+ default:
+ Process.Start(new ProcessStartInfo(mainExecutable, argument)
+ {
+ WorkingDirectory = Path.GetDirectoryName(mainExecutable)
+ });
+ break;
+ }
+ }
+ catch (Exception e)
+ {
+ var message = string.Format(Strings.ErrorStartingProcess, e.FormatSummary(true));
+ await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+
+ await Task.Delay(5000);
+ await Dispatcher.InvokeAsync(() =>
+ {
+ StartStudioCommand.IsEnabled = ActiveVersion is not null && ActiveVersion.CanStart;
+ //Save settings because launcher maybe have not been closed
+ LauncherSettings.ActiveVersion = ActiveVersion is not null ? ActiveVersion.Name : "";
+ LauncherSettings.Save();
+ });
+ }
+
+ private async Task InstallLatestVersion()
+ {
+ var latestVersion = strideVersions.FirstOrDefault();
+ // Should never happen
+ if (latestVersion is null || !latestVersion.CanBeDownloaded)
+ return;
+
+ if (latestVersion.IsProcessing)
+ {
+ await ServiceProvider.Get().MessageBoxAsync(Strings.InstallAlreadyInProgress, MessageBoxButton.OK, MessageBoxImage.Information);
+ InstallLatestVersionCommand.IsEnabled = false;
+ }
+
+ latestVersion.DownloadCommand.Execute();
+ }
+
+ private async Task OpenUrl(string url)
+ {
+ try
+ {
+ Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
+ }
+ // FIXME: catch only specific exceptions?
+ catch (Exception)
+ {
+ await ServiceProvider.Get().MessageBoxAsync(Strings.ErrorOpeningBrowser, MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private async Task FetchNewsPages()
+ {
+ var pages = await NewsPageViewModel.FetchNewsPages(ServiceProvider, 30);
+ var sortedPages = pages.OrderBy(x => x.Date).Reverse().ToList();
+ Dispatcher.Invoke(() => NewsPages = new(sortedPages));
+ }
+
+ public static bool HasDoneTask(string taskName)
+ {
+ // FIXME xplat-editor get that information from a config file on Linux (e.g. under /etc) and MacOS
+ if (OperatingSystem.IsWindows())
+ {
+ var localMachine32 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
+ using var subkey = localMachine32.OpenSubKey(@"SOFTWARE\Stride\");
+ if (subkey is not null)
+ {
+ var value = (string?)subkey.GetValue(taskName);
+ return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void SaveTaskAsDone(string taskName)
+ {
+ // FIXME xplat-editor store that information to a config file on Linux (e.g. under /etc) and MacOS
+ if (OperatingSystem.IsWindows())
+ {
+ var localMachine32 = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
+ using var subkey = localMachine32.CreateSubKey(@"SOFTWARE\Stride\");
+ subkey?.SetValue(taskName, "True");
+ }
+ }
+
+ private void DisplayReleaseAnnouncement()
+ {
+ }
+
+ void IPackagesLogger.Log(MessageLevel level, string message)
+ {
+ lock (logMessages)
+ {
+ logMessages.Add((DateTime.Now, level, message));
+ }
+ }
+
+ Task IPackagesLogger.LogAsync(MessageLevel level, string message)
+ {
+ ((IPackagesLogger)this).Log(level, message);
+ return Task.CompletedTask;
+ }
+}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/NewsPageViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/NewsPageViewModel.cs
index 567571128f..55b4108aae 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/NewsPageViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/NewsPageViewModel.cs
@@ -1,112 +1,110 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Collections.Generic;
+
using System.Diagnostics;
using System.Globalization;
-using System.Net.Http;
-using System.Threading.Tasks;
using System.Xml;
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.Services;
using Stride.Core.Presentation.ViewModels;
-using Stride.LauncherApp.Resources;
+using Stride.Launcher.Assets.Localization;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+public sealed class NewsPageViewModel : DispatcherViewModel
{
- internal class NewsPageViewModel : DispatcherViewModel
+ private static readonly HttpClient httpClient = new();
+
+ public NewsPageViewModel(IViewModelServiceProvider serviceProvider)
+ : base(serviceProvider)
+ {
+ OpenUrlCommand = new AnonymousTaskCommand(ServiceProvider, OpenUrl);
+ }
+
+ private async Task OpenUrl()
{
- private static readonly HttpClient httpClient = new();
+ if (Url is null) return;
- public NewsPageViewModel(IViewModelServiceProvider serviceProvider)
- : base(serviceProvider)
+ try
{
- OpenUrlCommand = new AnonymousTaskCommand(ServiceProvider, OpenUrl);
+ Process.Start(new ProcessStartInfo(Url) { UseShellExecute = true });
}
-
- private async Task OpenUrl()
+ catch (Exception)
{
- try
- {
- Process.Start(new ProcessStartInfo(Url) { UseShellExecute = true });
- }
- catch (Exception)
- {
- await ServiceProvider.Get().MessageBoxAsync(Strings.ErrorOpeningBrowser, MessageBoxButton.OK, MessageBoxImage.Error);
- }
+ await ServiceProvider.Get().MessageBoxAsync(Strings.ErrorOpeningBrowser, MessageBoxButton.OK, MessageBoxImage.Error);
}
+ }
- ///
- /// Gets or sets the title of this documentation page.
- ///
- public string Title { get; set; }
+ ///
+ /// Gets or sets the title of this documentation page.
+ ///
+ public string? Title { get; set; }
- ///
- /// Gets or sets the description of this documentation page.
- ///
- public string Description { get; set; }
+ ///
+ /// Gets or sets the description of this documentation page.
+ ///
+ public string? Description { get; set; }
- ///
- /// Gets or sets the url of this documentation page.
- ///
- public string Url { get; set; }
+ ///
+ /// Gets or sets the url of this documentation page.
+ ///
+ public string? Url { get; set; }
- ///
- /// Gets or sets the url of this documentation page.
- ///
- public DateTime Date { get; set; }
+ ///
+ /// Gets or sets the url of this documentation page.
+ ///
+ public DateTime Date { get; set; }
- ///
- /// Gets a command that will open the documentation page in the default web browser.
- ///
- public ICommandBase OpenUrlCommand { get; private set; }
+ ///
+ /// Gets a command that will open the documentation page in the default web browser.
+ ///
+ public ICommandBase OpenUrlCommand { get; private set; }
- public static async Task> FetchNewsPages(IViewModelServiceProvider serviceProvider, int maxCount)
+ public static async Task> FetchNewsPages(IViewModelServiceProvider serviceProvider, int maxCount)
+ {
+ var result = new List();
+ try
{
- var result = new List();
- try
- {
- using var response = await httpClient.GetAsync(Urls.RssFeed);
- response.EnsureSuccessStatusCode();
- var rss = await response.Content.ReadAsStreamAsync();
+ using var response = await httpClient.GetAsync(Urls.RssFeed);
+ response.EnsureSuccessStatusCode();
+ var rss = await response.Content.ReadAsStreamAsync();
- if (rss.Length == 0)
- return result;
+ if (rss.Length == 0)
+ return result;
- int count = 0;
- using XmlReader rssReader = XmlReader.Create(rss);
- rssReader.MoveToContent();
- while (rssReader.ReadToFollowing("item") && count < maxCount)
+ int count = 0;
+ using XmlReader rssReader = XmlReader.Create(rss, new XmlReaderSettings { Async = true });
+ await rssReader.MoveToContentAsync();
+ while (rssReader.ReadToFollowing("item") && count < maxCount)
+ {
+ rssReader.ReadToFollowing("title");
+ string? title = await rssReader.ReadAsync() ? rssReader.Value : null;
+ rssReader.ReadToFollowing("description");
+ string? description = await rssReader.ReadAsync() ? rssReader.Value : null;
+ rssReader.ReadToFollowing("pubDate");
+ var date = new DateTime();
+ bool dateValid = await rssReader.ReadAsync() && DateTime.TryParseExact(rssReader.Value, "ddd, dd MMM yyyy HH:mm:ss zz00", CultureInfo.InvariantCulture, DateTimeStyles.None, out date);
+ rssReader.ReadToFollowing("link");
+ string? link = await rssReader.ReadAsync() ? rssReader.Value : null;
+ if (dateValid && title is not null && link is not null && description is not null)
{
- rssReader.ReadToFollowing("title");
- string title = rssReader.Read() ? rssReader.Value : null;
- rssReader.ReadToFollowing("description");
- string description = rssReader.Read() ? rssReader.Value : null;
- rssReader.ReadToFollowing("pubDate");
- var date = new DateTime();
- bool dateValid = rssReader.Read() && DateTime.TryParseExact(rssReader.Value, "ddd, dd MMM yyyy HH:mm:ss zz00", CultureInfo.InvariantCulture, DateTimeStyles.None, out date);
- rssReader.ReadToFollowing("link");
- string link = rssReader.Read() ? rssReader.Value : null;
- if (dateValid && title != null && link != null && description != null)
+ var page = new NewsPageViewModel(serviceProvider)
{
- var page = new NewsPageViewModel(serviceProvider)
- {
- Title = title,
- Url = link,
- Description = description,
- Date = date
- };
- result.Add(page);
- ++count;
- }
+ Title = title,
+ Url = link,
+ Description = description,
+ Date = date
+ };
+ result.Add(page);
+ ++count;
}
}
- catch (Exception)
- {
- result.Clear();
- }
-
- return result;
}
+ catch (Exception)
+ {
+ result.Clear();
+ }
+
+ return result;
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/PackageVersionViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/PackageVersionViewModel.cs
index 495fc5d33b..f7bc1f9353 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/PackageVersionViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/PackageVersionViewModel.cs
@@ -1,304 +1,255 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Threading.Tasks;
+
+using System.Diagnostics;
using Stride.Core.Extensions;
using Stride.Core.Packages;
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.Services;
using Stride.Core.Presentation.ViewModels;
-using Stride.LauncherApp.Resources;
-using Stride.LauncherApp.Services;
+using Stride.Launcher.Assets.Localization;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+///
+/// A view model class that represents a Nuget package, as it exists both locally and on a remote server.
+///
+public abstract class PackageVersionViewModel : DispatcherViewModel
{
+ protected NugetLocalPackage? LocalPackage;
+ protected NugetServerPackage? ServerPackage;
+ private ProgressAction currentProgressAction;
+ private int currentProgress;
+ private bool isProcessing;
+ private bool canBeDownloaded;
+ private bool canDelete;
+ private string? currentProcessStatus;
+
///
- /// A view model class that represents a Nuget package, as it exists both locally and on a remote server.
+ /// Initializes a new instance of the class.
///
- internal abstract class PackageVersionViewModel : DispatcherViewModel
+ /// The parent instance.
+ /// The related instance.
+ /// The local package of this version, if a local package exists.
+ internal PackageVersionViewModel(MainViewModel launcher, NugetStore store, NugetLocalPackage? localPackage)
+ : base(launcher.SafeArgument(nameof(launcher)).ServiceProvider)
{
- protected NugetLocalPackage LocalPackage;
- protected NugetServerPackage ServerPackage;
- private ProgressAction currentProgressAction;
- private int currentProgress;
- private bool isProcessing;
- private bool canBeDownloaded;
- private bool canDelete;
- private string currentProcessStatus;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The parent instance.
- /// The related instance.
- /// The local package of this version, if a local package exists.
- internal PackageVersionViewModel(LauncherViewModel launcher, NugetStore store, NugetLocalPackage localPackage)
- : base(launcher.SafeArgument("launcher").ServiceProvider)
- {
- Launcher = launcher ?? throw new ArgumentNullException(nameof(launcher));
- Store = store ?? throw new ArgumentNullException(nameof(store));
- LocalPackage = localPackage;
- DownloadCommand = new AnonymousTaskCommand(ServiceProvider, () => Download(true));
- DeleteCommand = new AnonymousTaskCommand(ServiceProvider, () => Delete(true, true)) { IsEnabled = CanDelete };
- UpdateStatusInternal();
- }
+ ArgumentNullException.ThrowIfNull(launcher);
+ ArgumentNullException.ThrowIfNull(store);
+
+ Launcher = launcher;
+ Store = store;
+ LocalPackage = localPackage;
+ DownloadCommand = new AnonymousTaskCommand(ServiceProvider, () => Download(true));
+ DeleteCommand = new AnonymousTaskCommand(ServiceProvider, () => Delete(true, true)) { IsEnabled = CanDelete };
+ UpdateStatusInternal();
+ }
- ///
- /// Gets the short name of this version.
- ///
- public abstract string Name { get; }
-
- ///
- /// Gets the full name of this version.
- ///
- public abstract string FullName { get; }
-
- ///
- /// Gets the installation path of this version, or null if it is not installed.
- ///
- public virtual string InstallPath => LocalPackage?.Path;
-
- ///
- /// Gets whether a download is available for this version, being an update or a first install.
- ///
- public virtual bool CanBeDownloaded { get { return canBeDownloaded; } private set { SetValue(ref canBeDownloaded, value); } }
-
- ///
- /// Gets whether this package is installed and can be deleted.
- ///
- public virtual bool CanDelete { get { return canDelete; } private set { SetValue(ref canDelete, value); } }
-
- ///
- /// Gets the progress of the current download, in percents.
- ///
- public ProgressAction CurrentProgressAction { get { return currentProgressAction; } private set { SetValue(ref currentProgressAction, value); } }
-
- ///
- /// Gets the progress of the current download, in percents.
- ///
- public int CurrentProgress { get { return currentProgress; } private set { SetValue(ref currentProgress, value); } }
-
- ///
- /// Gets whether this version is being processed, being installed, upgraded or deleted.
- ///
- public bool IsProcessing { get { return isProcessing; } protected set { SetValue(ref isProcessing, value); } }
-
- ///
- /// Gets a string representing the current status while this version is being installed, upgraded or deleted.
- ///
- public string CurrentProcessStatus { get { return currentProcessStatus; } protected set { SetValue(ref currentProcessStatus, value); } }
-
- ///
- /// Gets the command that will download the latest version of the associated package and deploy it.
- ///
- public ICommandBase DownloadCommand { get; }
-
- ///
- /// Gets the command that will delete the associated package.
- ///
- public CommandBase DeleteCommand { get; }
-
- public LauncherViewModel Launcher { get; }
-
- ///
- /// Gets the related instance.
- ///
- protected NugetStore Store { get; }
-
- ///
- /// Gets the message to display when an error occurs during the install of this package.
- ///
- protected abstract string InstallErrorMessage { get; }
-
- ///
- /// Gets the message to display when an error occurs during the uninstall of this package.
- ///
- protected abstract string UninstallErrorMessage { get; }
-
- ///
- /// Updates all the versions of this type from the store. This method should update the and
- /// for each version of the same type, remove versions that do not exist anymore, and add new versions.
- ///
- /// A task that completes when the versions are updated.
- protected abstract Task UpdateVersionsFromStore();
-
- ///
- /// Updates the status of this version, synchronizing the different properties and command state of the view model with the local and server packages status.
- ///
- protected virtual void UpdateStatus()
- {
- UpdateStatusInternal();
- }
+ ///
+ /// Gets the short name of this version.
+ ///
+ public abstract string Name { get; }
- protected void UpdateProgress(ProgressAction action, int progress)
- {
- CurrentProgressAction = action;
- CurrentProgress = progress;
- UpdateInstallStatus();
- }
+ ///
+ /// Gets the full name of this version.
+ ///
+ public abstract string FullName { get; }
- ///
- /// Updates the property according to the value.
- ///
- protected abstract void UpdateInstallStatus();
+ ///
+ /// Gets the installation path of this version, or null if it is not installed.
+ ///
+ public virtual string? InstallPath => LocalPackage?.Path;
- ///
- /// Executes some actions before starting to download this version.
- ///
- protected virtual void BeforeDownload()
- {
- // Intentionally does nothing.
- }
+ ///
+ /// Gets whether a download is available for this version, being an update or a first install.
+ ///
+ public virtual bool CanBeDownloaded { get { return canBeDownloaded; } private set { SetValue(ref canBeDownloaded, value); } }
- ///
- /// Executes some actions after downloading and installing this version.
- ///
- protected virtual void AfterDownload()
- {
- // Intentionally does nothing.
- }
+ ///
+ /// Gets whether this package is installed and can be deleted.
+ ///
+ public virtual bool CanDelete { get { return canDelete; } private set { SetValue(ref canDelete, value); } }
- ///
- /// Downloads the latest version of this package. If a version is already in the local store, it will be deleted first.
- ///
- /// Indicates whether to display error message boxes when an error occurs.
- /// A task that completes when the latest version has been downloaded.
- ///
- /// This method will invoke, from a worker thread, before doing anything, and
- /// if the download successfully completed without exception. In every case, it will also invoke
- /// before completing.
- ///
- public Task Download(bool displayErrorMessage)
- {
- BeforeDownload();
+ ///
+ /// Gets the progress of the current download, in percents.
+ ///
+ public ProgressAction CurrentProgressAction { get { return currentProgressAction; } private set { SetValue(ref currentProgressAction, value); } }
- return Task.Run(async () =>
- {
- IsProcessing = true;
+ ///
+ /// Gets the progress of the current download, in percents.
+ ///
+ public int CurrentProgress { get { return currentProgress; } private set { SetValue(ref currentProgress, value); } }
- // Uninstall previous version first, if it exists
- if (LocalPackage != null)
- {
- try
- {
- CurrentProcessStatus = null;
- using var progressReport = new ProgressReport(Store, ServerPackage);
- progressReport.ProgressChanged += (action, progress) => { Dispatcher.InvokeAsync(() => { UpdateProgress(action, progress); }).Forget(); };
- progressReport.UpdateProgress(ProgressAction.Delete, -1);
- await Store.UninstallPackage(LocalPackage, progressReport);
- CurrentProcessStatus = null;
- }
- catch (Exception e)
- {
- if (displayErrorMessage)
- {
- var message = $"{UninstallErrorMessage}{e.FormatSummary(true)}";
- await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
- await UpdateVersionsFromStore();
- IsProcessing = false;
- return;
- }
+ ///
+ /// Gets whether this version is being processed, being installed, upgraded or deleted.
+ ///
+ public bool IsProcessing { get { return isProcessing; } protected set { SetValue(ref isProcessing, value); } }
- IsProcessing = false;
- throw;
- }
- }
+ ///
+ /// Gets a string representing the current status while this version is being installed, upgraded or deleted.
+ ///
+ public string? CurrentProcessStatus { get { return currentProcessStatus; } protected set { SetValue(ref currentProcessStatus, value); } }
- // Then download and install the latest version.
- bool downloadCompleted = false;
+ ///
+ /// Gets the command that will download the latest version of the associated package and deploy it.
+ ///
+ public ICommandBase DownloadCommand { get; }
+
+ ///
+ /// Gets the command that will delete the associated package.
+ ///
+ public CommandBase DeleteCommand { get; }
+
+ public MainViewModel Launcher { get; }
+
+ ///
+ /// Gets the related instance.
+ ///
+ protected NugetStore Store { get; }
+
+ ///
+ /// Gets the message to display when an error occurs during the install of this package.
+ ///
+ protected abstract string InstallErrorMessage { get; }
+
+ ///
+ /// Gets the message to display when an error occurs during the uninstall of this package.
+ ///
+ protected abstract string UninstallErrorMessage { get; }
+
+ ///
+ /// Updates all the versions of this type from the store. This method should update the and
+ /// for each version of the same type, remove versions that do not exist anymore, and add new versions.
+ ///
+ /// A task that completes when the versions are updated.
+ protected abstract Task UpdateVersionsFromStore();
+
+ ///
+ /// Updates the status of this version, synchronizing the different properties and command state of the view model with the local and server packages status.
+ ///
+ protected virtual void UpdateStatus()
+ {
+ UpdateStatusInternal();
+ }
+
+ protected void UpdateProgress(ProgressAction action, int progress)
+ {
+ CurrentProgressAction = action;
+ CurrentProgress = progress;
+ UpdateInstallStatus();
+ }
+
+ ///
+ /// Updates the property according to the value.
+ ///
+ protected abstract void UpdateInstallStatus();
+
+ ///
+ /// Executes some actions before starting to download this version.
+ ///
+ protected virtual void BeforeDownload()
+ {
+ // Intentionally does nothing.
+ }
+
+ ///
+ /// Executes some actions after downloading and installing this version.
+ ///
+ protected virtual void AfterDownload()
+ {
+ // Intentionally does nothing.
+ }
+
+ ///
+ /// Downloads the latest version of this package. If a version is already in the local store, it will be deleted first.
+ ///
+ /// Indicates whether to display error message boxes when an error occurs.
+ /// A task that completes when the latest version has been downloaded.
+ ///
+ /// This method will invoke, from a worker thread, before doing anything, and
+ /// if the download successfully completed without exception. In every case, it will also invoke
+ /// before completing.
+ ///
+ public Task Download(bool displayErrorMessage)
+ {
+ BeforeDownload();
+
+ return Task.Run(async () =>
+ {
+ IsProcessing = true;
+ Debug.Assert(ServerPackage is not null);
+
+ // Uninstall previous version first, if it exists
+ if (LocalPackage is not null)
+ {
try
{
- using (var progressReport = new ProgressReport(Store, ServerPackage))
- {
- progressReport.ProgressChanged += (action, progress) => { Dispatcher.InvokeAsync(() => { UpdateProgress(action, progress); }).Forget(); };
- progressReport.UpdateProgress(ProgressAction.Install, -1);
- MetricsHelper.NotifyDownloadStarting(ServerPackage.Id, ServerPackage.Version.ToString());
- await Store.InstallPackage(ServerPackage.Id, ServerPackage.Version, ServerPackage.TargetFrameworks, progressReport);
- downloadCompleted = true;
- MetricsHelper.NotifyDownloadCompleted(ServerPackage.Id, ServerPackage.Version.ToString());
- }
-
- AfterDownload();
+ CurrentProcessStatus = null;
+ using var progressReport = new ProgressReport(Store, ServerPackage);
+ progressReport.ProgressChanged += (action, progress) => { Dispatcher.InvokeAsync(() => { UpdateProgress(action, progress); }).Forget(); };
+ progressReport.UpdateProgress(ProgressAction.Delete, -1);
+ await Store.UninstallPackage(LocalPackage, progressReport);
+ CurrentProcessStatus = null;
}
catch (Exception e)
{
- if (!downloadCompleted)
- MetricsHelper.NotifyDownloadFailed(ServerPackage.Id, ServerPackage.Version.ToString());
-
- // Rollback: try to delete the broken package (i.e. if it is installed with NuGet but had a failure during Install scripts)
- try
- {
- var localPackage = Store.FindLocalPackage(ServerPackage.Id, ServerPackage.Version);
- if (localPackage != null)
- {
- await Store.UninstallPackage(localPackage, null);
- }
- }
- catch
- {
- // Note: quite a bad state: rollback (uninstall) failed
- // we don't display the message to not confuse the user even more with an intermediate uninstall error message before the install error message
- }
-
if (displayErrorMessage)
{
- var message = $@"**{InstallErrorMessage}**
-### Log
-```
-{Launcher.LogMessages}
-```
-
-### Exception
-```
-{e.FormatSummary(false).TrimEnd(Environment.NewLine.ToCharArray())}
-```";
+ var message = $"{UninstallErrorMessage}{e.FormatSummary(true)}";
await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
+ await UpdateVersionsFromStore();
+ IsProcessing = false;
return;
}
- throw;
- }
- finally
- {
- await UpdateVersionsFromStore();
+
IsProcessing = false;
+ throw;
}
- });
- }
-
- protected async Task Delete(bool displayErrorMessage, bool askConfirmation)
- {
- bool proceed = !askConfirmation;
- if (askConfirmation)
- {
- var message = string.Format(Strings.ConfirmUninstall, FullName);
- var confirmResult = await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.YesNo);
- proceed = confirmResult == MessageBoxResult.Yes;
- }
- if (proceed)
- {
- await Task.Run(() => DeleteInternal(displayErrorMessage));
}
- }
- private async Task DeleteInternal(bool displayErrorMessage)
- {
- IsProcessing = true;
+ // Then download and install the latest version.
+ bool downloadCompleted = false;
try
{
using (var progressReport = new ProgressReport(Store, ServerPackage))
{
progressReport.ProgressChanged += (action, progress) => { Dispatcher.InvokeAsync(() => { UpdateProgress(action, progress); }).Forget(); };
- progressReport.UpdateProgress(ProgressAction.Delete, -1);
- CurrentProcessStatus = string.Format(Strings.ReportDeletingVersion, FullName);
- await Store.UninstallPackage(LocalPackage, progressReport);
- CurrentProcessStatus = null;
+ progressReport.UpdateProgress(ProgressAction.Install, -1);
+ await Store.InstallPackage(ServerPackage.Id, ServerPackage.Version, ServerPackage.TargetFrameworks, progressReport);
+ downloadCompleted = true;
}
+
+ AfterDownload();
}
catch (Exception e)
{
+ // Rollback: try to delete the broken package (i.e. if it is installed with NuGet but had a failure during Install scripts)
+ try
+ {
+ var localPackage = Store.FindLocalPackage(ServerPackage.Id, ServerPackage.Version);
+ if (localPackage is not null)
+ {
+ await Store.UninstallPackage(localPackage, null);
+ }
+ }
+ catch
+ {
+ // Note: quite a bad state: rollback (uninstall) failed
+ // we don't display the message to not confuse the user even more with an intermediate uninstall error message before the install error message
+ }
+
if (displayErrorMessage)
{
- var message = $"{UninstallErrorMessage}{e.FormatSummary(true)}";
+ var message = $@"**{InstallErrorMessage}**
+### Log
+```
+{Launcher.LogMessages}
+```
+
+### Exception
+```
+{e.FormatSummary(false).TrimEnd(Environment.NewLine.ToCharArray())}
+```";
await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
@@ -309,13 +260,58 @@ private async Task DeleteInternal(bool displayErrorMessage)
await UpdateVersionsFromStore();
IsProcessing = false;
}
+ });
+ }
+
+ protected async Task Delete(bool displayErrorMessage, bool askConfirmation)
+ {
+ bool proceed = !askConfirmation;
+ if (askConfirmation)
+ {
+ var message = string.Format(Strings.ConfirmUninstall, FullName);
+ var confirmResult = await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.YesNo);
+ proceed = confirmResult == MessageBoxResult.Yes;
}
+ if (proceed)
+ {
+ await Task.Run(() => DeleteInternal(displayErrorMessage));
+ }
+ }
- private void UpdateStatusInternal()
+ private async Task DeleteInternal(bool displayErrorMessage)
+ {
+ IsProcessing = true;
+ Debug.Assert(LocalPackage is not null);
+ try
+ {
+ using var progressReport = new ProgressReport(Store, ServerPackage);
+ progressReport.ProgressChanged += (action, progress) => { Dispatcher.InvokeAsync(() => { UpdateProgress(action, progress); }).Forget(); };
+ progressReport.UpdateProgress(ProgressAction.Delete, -1);
+ CurrentProcessStatus = string.Format(Strings.ReportDeletingVersion, FullName);
+ await Store.UninstallPackage(LocalPackage, progressReport);
+ CurrentProcessStatus = null;
+ }
+ catch (Exception e)
+ {
+ if (displayErrorMessage)
+ {
+ var message = $"{UninstallErrorMessage}{e.FormatSummary(true)}";
+ await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+ throw;
+ }
+ finally
{
- CanBeDownloaded = (LocalPackage == null && ServerPackage != null) || (LocalPackage != null && ServerPackage != null && LocalPackage.Version < ServerPackage.Version);
- CanDelete = LocalPackage != null;
- DownloadCommand.IsEnabled = CanBeDownloaded;
+ await UpdateVersionsFromStore();
+ IsProcessing = false;
}
}
+
+ private void UpdateStatusInternal()
+ {
+ CanBeDownloaded = (LocalPackage is null && ServerPackage is not null) || (LocalPackage is not null && ServerPackage is not null && LocalPackage.Version < ServerPackage.Version);
+ CanDelete = LocalPackage is not null;
+ DownloadCommand.IsEnabled = CanBeDownloaded;
+ }
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/RecentProjectViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/RecentProjectViewModel.cs
index 0d4670c987..594abe2aaf 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/RecentProjectViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/RecentProjectViewModel.cs
@@ -1,9 +1,7 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
+
using System.Diagnostics;
-using System.Linq;
-using System.Threading.Tasks;
using Stride.Core.Assets;
using Stride.Core.Extensions;
using Stride.Core.IO;
@@ -11,123 +9,122 @@
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.Services;
using Stride.Core.Presentation.ViewModels;
-using Stride.LauncherApp.Resources;
-using Stride.LauncherApp.Services;
+using Stride.Launcher.Assets.Localization;
+using Stride.Launcher.Services;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+public sealed class RecentProjectViewModel : DispatcherViewModel
{
- internal class RecentProjectViewModel : DispatcherViewModel
- {
- private readonly UFile fullPath;
- private string strideVersionName;
- private Version strideVersion;
+ private readonly UFile fullPath;
+ private string strideVersionName;
+ private Version? strideVersion;
- internal RecentProjectViewModel(LauncherViewModel launcher, UFile path)
- : base(launcher.SafeArgument(nameof(launcher)).ServiceProvider)
- {
- Name = path.GetFileNameWithoutExtension();
- Launcher = launcher;
- fullPath = path;
- StrideVersionName = Strings.ReportDiscovering;
- OpenCommand = new AnonymousTaskCommand(ServiceProvider, () => OpenWith(null)) { IsEnabled = false };
- OpenWithCommand = new AnonymousTaskCommand(ServiceProvider, OpenWith);
- ExploreCommand = new AnonymousCommand(ServiceProvider, Explore);
- RemoveCommand = new AnonymousCommand(ServiceProvider, Remove);
- CompatibleVersions = new ObservableList();
- DiscoverStrideVersion();
- }
+ internal RecentProjectViewModel(MainViewModel launcher, UFile path)
+ : base(launcher.SafeArgument(nameof(launcher)).ServiceProvider)
+ {
+ Name = path.GetFileNameWithoutExtension();
+ Launcher = launcher;
+ fullPath = path;
+ strideVersionName = Strings.ReportDiscovering;
+ OpenCommand = new AnonymousTaskCommand(ServiceProvider, () => OpenWith(null)) { IsEnabled = false };
+ OpenWithCommand = new AnonymousTaskCommand(ServiceProvider, OpenWith);
+ ExploreCommand = new AnonymousCommand(ServiceProvider, Explore);
+ RemoveCommand = new AnonymousCommand(ServiceProvider, Remove);
+ CompatibleVersions = [];
+ DiscoverStrideVersion();
+ }
- public string Name { get; private set; }
+ public string Name { get; private set; }
- public string FullPath => fullPath.ToOSPath();
+ public string FullPath => fullPath.ToOSPath();
- public string StrideVersionName { get { return strideVersionName; } private set { SetValue(ref strideVersionName, value); } }
+ public string StrideVersionName { get { return strideVersionName; } private set { SetValue(ref strideVersionName, value); } }
- public Version StrideVersion { get { return strideVersion; } private set { SetValue(ref strideVersion, value); } }
+ public Version? StrideVersion { get { return strideVersion; } private set { SetValue(ref strideVersion, value); } }
- public LauncherViewModel Launcher { get; }
+ public MainViewModel Launcher { get; }
- public ObservableList CompatibleVersions { get; private set; }
+ public ObservableList CompatibleVersions { get; private set; }
- public ICommandBase ExploreCommand { get; }
+ public ICommandBase ExploreCommand { get; }
- public ICommandBase OpenCommand { get; }
+ public ICommandBase OpenCommand { get; }
- public ICommandBase OpenWithCommand { get; }
+ public ICommandBase OpenWithCommand { get; }
- public ICommandBase RemoveCommand { get; }
+ public ICommandBase RemoveCommand { get; }
- private void DiscoverStrideVersion()
+ private void DiscoverStrideVersion()
+ {
+ Task.Run(async () =>
{
- Task.Run(async () =>
- {
- var packageVersion = await PackageSessionHelper.GetPackageVersion(fullPath);
- StrideVersion = packageVersion != null ? new Version(packageVersion.Version.Major, packageVersion.Version.Minor) : null;
- StrideVersionName = StrideVersion?.ToString();
+ var packageVersion = await PackageSessionHelper.GetPackageVersion(fullPath);
+ StrideVersion = packageVersion is not null ? new Version(packageVersion.Version.Major, packageVersion.Version.Minor) : null;
+ StrideVersionName = StrideVersion?.ToString();
- Dispatcher.Invoke(() => OpenCommand.IsEnabled = StrideVersionName != null);
- });
- }
+ Dispatcher.Invoke(() => OpenCommand.IsEnabled = StrideVersionName is not null);
+ });
+ }
- private void Explore()
- {
- var startInfo = new ProcessStartInfo("explorer.exe", $"/select,{fullPath.ToOSPath()}") { UseShellExecute = true };
- var explorer = new Process { StartInfo = startInfo };
- explorer.Start();
- }
+ private void Explore()
+ {
+ var startInfo = new ProcessStartInfo("explorer.exe", $"/select,{fullPath.ToOSPath()}") { UseShellExecute = true };
+ var explorer = new Process { StartInfo = startInfo };
+ explorer.Start();
+ }
- private void Remove()
+ private void Remove()
+ {
+ //Remove files that's was deleted or upgraded by stride versions <= 3.0
+ if (string.IsNullOrEmpty(StrideVersionName) || string.Compare(StrideVersionName, "3.0", StringComparison.Ordinal) <= 0)
{
- //Remove files that's was deleted or upgraded by stride versions <= 3.0
- if (string.IsNullOrEmpty(StrideVersionName) || string.Compare(StrideVersionName, "3.0", StringComparison.Ordinal) <= 0)
- {
- //Get all installed versions
- var strideInstalledVersions = Launcher.StrideVersions.Where(x => x.CanDelete)
- .Select(x => $"{x.Major}.{x.Minor}").ToList();
-
- //If original version of files is not in list get and to add it.
- if (!string.IsNullOrEmpty(StrideVersionName) && !strideInstalledVersions.Any(x => x.Equals(StrideVersionName)))
- strideInstalledVersions.Add(StrideVersionName);
-
- foreach (var item in strideInstalledVersions)
- {
- GameStudioSettings.RemoveMostRecentlyUsed(fullPath, item);
- }
- }
- else
+ //Get all installed versions
+ var strideInstalledVersions = Launcher.StrideVersions.Where(x => x.CanDelete)
+ .Select(x => $"{x.Major}.{x.Minor}").ToList();
+
+ //If original version of files is not in list get and to add it.
+ if (!string.IsNullOrEmpty(StrideVersionName) && !strideInstalledVersions.Any(x => x.Equals(StrideVersionName)))
+ strideInstalledVersions.Add(StrideVersionName);
+
+ foreach (var item in strideInstalledVersions)
{
- GameStudioSettings.RemoveMostRecentlyUsed(fullPath, StrideVersionName);
+ GameStudioSettings.RemoveMostRecentlyUsed(fullPath, item);
}
}
+ else
+ {
+ GameStudioSettings.RemoveMostRecentlyUsed(fullPath, StrideVersionName);
+ }
+ }
- private async Task OpenWith(StrideVersionViewModel version)
+ private async Task OpenWith(StrideVersionViewModel? version)
+ {
+ string message;
+ version ??= Launcher.StrideVersions.FirstOrDefault(x => new Version(x.Major, x.Minor) == StrideVersion);
+ if (version is null)
{
- string message;
- version = version ?? Launcher.StrideVersions.FirstOrDefault(x => new Version(x.Major, x.Minor) == StrideVersion);
- if (version == null)
- {
- message = string.Format(Strings.ErrorDoNotFindVersion, StrideVersion);
- await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Information);
- return;
- }
- if (version.IsProcessing)
- {
- message = string.Format(Strings.ErrorVersionBeingUpdated, StrideVersion);
- await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Information);
- return;
- }
- if (!version.CanDelete)
+ message = string.Format(Strings.ErrorDoNotFindVersion, StrideVersion);
+ await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+ if (version.IsProcessing)
+ {
+ message = string.Format(Strings.ErrorVersionBeingUpdated, StrideVersion);
+ await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Information);
+ return;
+ }
+ if (!version.CanDelete)
+ {
+ message = string.Format(Strings.ErrorVersionNotInstalled, StrideVersion);
+ var result = await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.YesNoCancel, MessageBoxImage.Information);
+ if (result == MessageBoxResult.Yes)
{
- message = string.Format(Strings.ErrorVersionNotInstalled, StrideVersion);
- var result = await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.YesNoCancel, MessageBoxImage.Information);
- if (result == MessageBoxResult.Yes)
- {
- version.DownloadCommand.Execute();
- }
- return;
+ version.DownloadCommand.Execute();
}
- Launcher.ActiveVersion = version;
- Launcher.StartStudio($"\"{FullPath}\"");
+ return;
}
+ Launcher.ActiveVersion = version;
+ Launcher.StartStudio($"\"{FullPath}\"").Forget();
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/ReleaseNotesViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/ReleaseNotesViewModel.cs
index d397e506b3..fc4d1156ef 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/ReleaseNotesViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/ReleaseNotesViewModel.cs
@@ -1,94 +1,116 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.IO;
-using System.Net.Http;
+
using System.Text.RegularExpressions;
-using Stride.Core.Annotations;
using Stride.Core.Extensions;
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+namespace Stride.Launcher.ViewModels;
+
+///
+/// This class represents the release notes of a given version.
+///
+public sealed partial class ReleaseNotesViewModel : DispatcherViewModel
{
- ///
- /// This class represents the release notes of a given version.
- ///
- internal class ReleaseNotesViewModel : DispatcherViewModel
- {
- private static readonly HttpClient httpClient = new();
+ private static readonly HttpClient httpClient = new();
- private readonly LauncherViewModel launcher;
- private bool isActive;
- private string markdownContent;
- private bool isLoading = true;
- private bool isLoaded;
- private bool isUnavailable;
+ private readonly MainViewModel launcher;
+ private bool isActive;
+ private string? markdownContent;
+ private bool isLoading = true;
+ private bool isLoaded;
+ private bool isUnavailable;
- private const string RootUrl = "https://doc.stride3d.net";
- private const string ReleaseNotesFileName = "ReleaseNotes.md";
- private string baseUrl;
+ private const string RootUrl = "https://doc.stride3d.net";
+ private const string ReleaseNotesFileName = "ReleaseNotes.md";
+ private readonly string baseUrl;
- internal ReleaseNotesViewModel([NotNull] LauncherViewModel launcher, [NotNull] string version)
- : base(launcher.SafeArgument(nameof(launcher)).ServiceProvider)
- {
- if (version == null) throw new ArgumentNullException(nameof(version));
- this.launcher = launcher;
+ internal ReleaseNotesViewModel(MainViewModel launcher, string version)
+ : base(launcher.SafeArgument(nameof(launcher)).ServiceProvider)
+ {
+ ArgumentNullException.ThrowIfNull(launcher);
+ ArgumentNullException.ThrowIfNull(version);
- Version = version;
- baseUrl = $"{RootUrl}/{Version}/ReleaseNotes/";
+ this.launcher = launcher;
+ Version = version;
+ baseUrl = $"{RootUrl}/{Version}/ReleaseNotes/";
#if DEBUG
- if (Environment.CommandLine.ToLowerInvariant().Contains("/previewreleasenotes"))
+ if (Environment.CommandLine.ToLowerInvariant().Contains("/previewreleasenotes"))
+ {
+ var launcherPath = AppDomain.CurrentDomain.BaseDirectory;
+ var mdPath = Path.Combine(launcherPath, @"..\..\..\..\..\doc\");
+ if (File.Exists($"{mdPath}{ReleaseNotesFileName}"))
{
- var launcherPath = AppDomain.CurrentDomain.BaseDirectory;
- var mdPath = Path.Combine(launcherPath, @"..\..\..\..\..\doc\");
- if (File.Exists($"{mdPath}{ReleaseNotesFileName}"))
- {
- baseUrl = $"file:///{mdPath.Replace("\\", "/")}";
- }
+ baseUrl = $"file:///{mdPath.Replace("\\", "/")}";
}
+ }
#endif
- ToggleCommand = new AnonymousCommand(ServiceProvider, Toggle);
- }
+ ToggleCommand = new AnonymousCommand(ServiceProvider, Toggle);
+ }
- public string BaseUrl { get { return baseUrl; } }
+ public string BaseUrl { get { return baseUrl; } }
- public string Version { get; }
+ public string Version { get; }
- public string MarkdownContent { get { return markdownContent; } private set { SetValue(ref markdownContent, value); } }
+ public string? MarkdownContent { get { return markdownContent; } private set { SetValue(ref markdownContent, value); } }
- public bool IsActive { get { return isActive; } private set { SetValue(ref isActive, value); } }
+ public bool IsActive { get { return isActive; } private set { SetValue(ref isActive, value); } }
- public bool IsLoading { get { return isLoading; } set { SetValue(ref isLoading, value); } }
+ public bool IsLoading { get { return isLoading; } set { SetValue(ref isLoading, value); } }
- public bool IsLoaded { get { return isLoaded; } set { SetValue(ref isLoaded, value); } }
+ public bool IsLoaded { get { return isLoaded; } set { SetValue(ref isLoaded, value); } }
- public bool IsUnavailable { get { return isUnavailable; } set { SetValue(ref isUnavailable, value); } }
+ public bool IsUnavailable { get { return isUnavailable; } set { SetValue(ref isUnavailable, value); } }
- public ICommandBase ToggleCommand { get; private set; }
+ public ICommandBase ToggleCommand { get; private set; }
+
+ public async void FetchReleaseNotes()
+ {
+ string releaseNotesMarkdown;
- public async void FetchReleaseNotes()
+ try
{
- string releaseNotesMarkdown;
+ using var response = await httpClient.GetAsync($"{BaseUrl}{ReleaseNotesFileName}");
+ response.EnsureSuccessStatusCode();
+ releaseNotesMarkdown = await response.Content.ReadAsStringAsync();
+ }
+ catch (Exception)
+ {
+ IsLoading = false;
+ IsUnavailable = true;
+ return;
+ }
- try
- {
- using var response = await httpClient.GetAsync($"{BaseUrl}{ReleaseNotesFileName}");
- response.EnsureSuccessStatusCode();
- releaseNotesMarkdown = await response.Content.ReadAsStringAsync();
- }
- catch (Exception)
- {
- IsLoading = false;
- IsUnavailable = true;
- return;
- }
+ if (releaseNotesMarkdown is not null)
+ {
+ // parse video tag
+ var videoRegex = GetVideoRegex();
+ MarkdownContent = videoRegex.Replace(releaseNotesMarkdown, "\r\n\r\n[_Click to watch the video_]($4)");
+ IsLoading = false;
+ IsLoaded = true;
+ }
+ else
+ {
+ IsLoading = false;
+ IsUnavailable = true;
+ }
+ }
- if (releaseNotesMarkdown != null)
- {
- // parse video tag
- var videoRegex = new Regex(@"
+ public void Show()
+ {
+ IsActive = true;
+ launcher.ActiveReleaseNotes = this;
+ }
+
+ private void Toggle()
+ {
+ IsActive = launcher.ActiveReleaseNotes != this || !IsActive;
+ launcher.ActiveReleaseNotes = this;
+ }
+
+ [GeneratedRegex(@"
",
- RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
- MarkdownContent = videoRegex.Replace(releaseNotesMarkdown, "\r\n\r\n[_Click to watch the video_]($4)");
- IsLoading = false;
- IsLoaded = true;
- }
- else
- {
- IsLoading = false;
- IsUnavailable = true;
- }
- }
-
- public void Show()
- {
- IsActive = true;
- launcher.ActiveReleaseNotes = this;
- }
-
- private void Toggle()
- {
- IsActive = launcher.ActiveReleaseNotes != this || !IsActive;
- launcher.ActiveReleaseNotes = this;
- }
- }
+ ", RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace)]
+ private static partial Regex GetVideoRegex();
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/StrideDevVersionViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/StrideDevVersionViewModel.cs
index 2fd5eb24c3..450ba93f50 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/StrideDevVersionViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/StrideDevVersionViewModel.cs
@@ -1,79 +1,78 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System.Threading.Tasks;
+
using Stride.Core.Annotations;
using Stride.Core.IO;
using Stride.Core.Packages;
-namespace Stride.LauncherApp.ViewModels
+namespace Stride.Launcher.ViewModels;
+
+///
+/// An implementation of the that represents a non-official version locally built.
+///
+public sealed class StrideDevVersionViewModel : StrideVersionViewModel
{
- ///
- /// An implementation of the that represents a non-official version locally built.
- ///
- internal class StrideDevVersionViewModel : StrideVersionViewModel
+ private readonly UDirectory path;
+ private static int devMinorCounter = int.MaxValue;
+ private readonly NugetLocalPackage localPackage;
+ private readonly bool isDevRedirect;
+
+ internal StrideDevVersionViewModel(MainViewModel launcher, NugetStore store, [CanBeNull] NugetLocalPackage localPackage, UDirectory path, bool isDevRedirect)
+ : base(launcher, store, localPackage, localPackage.Id, int.MaxValue, devMinorCounter--)
+ {
+ this.path = path;
+ this.localPackage = localPackage;
+ this.isDevRedirect = isDevRedirect;
+ DownloadCommand.IsEnabled = false;
+ // Update initial status (IsVisible will be set to true)
+ UpdateStatus();
+ }
+
+ ///
+ public override string Name => "Local " + path.MakeRelative(path.GetParent());
+
+ ///
+ public override string DisplayName => localPackage is not null ? $"{PackageSimpleName} {localPackage.Version} (local)" : base.DisplayName;
+
+ ///
+ public override string FullName => localPackage?.Version.ToString() ?? path.MakeRelative(path.GetParent());
+
+ ///
+ public override bool CanBeDownloaded => false;
+
+ // TODO: a distinction between CanDelete and IsInstalled?
+ ///
+ public override bool CanDelete => isDevRedirect;
+
+ ///
+ public override string InstallPath => path.ToOSPath();
+
+
+ // This property is not used because a dev version cannot be downloaded.
+ ///
+ protected override string InstallErrorMessage => string.Empty;
+
+ // This property is not used because a dev version cannot be downloaded.
+ ///
+ protected override string UninstallErrorMessage => string.Empty;
+
+ ///
+ protected override Task UpdateVersionsFromStore()
+ {
+ return Launcher.RetrieveLocalStrideVersions();
+ }
+
+ ///
+ protected override void UpdateStatus()
+ {
+ base.UpdateStatus();
+ // A dev version is always local and cannot be downloaded
+ DownloadCommand.IsEnabled = false;
+ }
+
+ ///
+ protected override void UpdateInstallStatus()
{
- private readonly UDirectory path;
- private static int devMinorCounter = int.MaxValue;
- private NugetLocalPackage localPackage;
- private bool isDevRedirect;
-
- internal StrideDevVersionViewModel(LauncherViewModel launcher, NugetStore store, [CanBeNull] NugetLocalPackage localPackage, UDirectory path, bool isDevRedirect)
- : base(launcher, store, localPackage, localPackage.Id, int.MaxValue, devMinorCounter--)
- {
- this.path = path;
- this.localPackage = localPackage;
- this.isDevRedirect = isDevRedirect;
- DownloadCommand.IsEnabled = false;
- // Update initial status (IsVisible will be set to true)
- UpdateStatus();
- }
-
- ///
- public override string Name => "Local " + path.MakeRelative(path.GetParent());
-
- ///
- public override string DisplayName => localPackage != null ? $"{PackageSimpleName} {localPackage.Version} (local)" : base.DisplayName;
-
- ///
- public override string FullName => localPackage?.Version.ToString() ?? path.MakeRelative(path.GetParent());
-
- ///
- public override bool CanBeDownloaded => false;
-
- // TODO: a distinction between CanDelete and IsInstalled?
- ///
- public override bool CanDelete => isDevRedirect;
-
- ///
- public override string InstallPath => path.ToOSPath();
-
-
- // This property is not used because a dev verison cannot be downloaded.
- ///
- protected override string InstallErrorMessage => null;
-
- // This property is not used because a dev verison cannot be downloaded.
- ///
- protected override string UninstallErrorMessage => null;
-
- ///
- protected override Task UpdateVersionsFromStore()
- {
- return Launcher.RetrieveLocalStrideVersions();
- }
-
- ///
- protected override void UpdateStatus()
- {
- base.UpdateStatus();
- // A dev version is always local and cannot be downloaded
- DownloadCommand.IsEnabled = false;
- }
-
- ///
- protected override void UpdateInstallStatus()
- {
- // A dev version cannot be installed
- }
+ // A dev version cannot be installed
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/StrideStoreAlternateVersionViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/StrideStoreAlternateVersionViewModel.cs
index a9791442eb..d81b2fddfd 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/StrideStoreAlternateVersionViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/StrideStoreAlternateVersionViewModel.cs
@@ -1,67 +1,65 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
using Stride.Core;
-using Stride.Core.Annotations;
using Stride.Core.Packages;
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+namespace Stride.Launcher.ViewModels;
+
+public sealed class StrideStoreAlternateVersionViewModel : DispatcherViewModel
{
- internal sealed class StrideStoreAlternateVersionViewModel : DispatcherViewModel
- {
- private StrideStoreVersionViewModel strideVersion;
- internal NugetServerPackage ServerPackage;
- internal NugetLocalPackage LocalPackage;
+ internal NugetServerPackage ServerPackage;
+ internal NugetLocalPackage LocalPackage;
- public StrideStoreAlternateVersionViewModel([NotNull] StrideStoreVersionViewModel strideVersion)
- : base(strideVersion.ServiceProvider)
+ public StrideStoreAlternateVersionViewModel(StrideStoreVersionViewModel strideVersion)
+ : base(strideVersion.ServiceProvider)
+ {
+ SetAsActiveCommand = new AnonymousCommand(ServiceProvider, () =>
{
- this.strideVersion = strideVersion;
-
- SetAsActiveCommand = new AnonymousCommand(ServiceProvider, () =>
+ strideVersion.UpdateLocalPackage(LocalPackage, null);
+ if (LocalPackage is null)
{
- strideVersion.UpdateLocalPackage(LocalPackage, null);
- if (LocalPackage == null)
- {
- // If it's a non installed version, offer same version for serverPackage so that it offers to install this specific version
- strideVersion.UpdateServerPackage(ServerPackage, null);
- }
- else
- {
- // Otherwise, offer latest version for update
- strideVersion.UpdateServerPackage(strideVersion.LatestServerPackage, null);
- }
+ // If it's a non installed version, offer same version for serverPackage so that it offers to install this specific version
+ strideVersion.UpdateServerPackage(ServerPackage, null);
+ }
+ else
+ {
+ // Otherwise, offer latest version for update
+ strideVersion.UpdateServerPackage(strideVersion.LatestServerPackage, null);
+ }
- strideVersion.Launcher.ActiveVersion = strideVersion;
- });
- }
+ strideVersion.Launcher.ActiveVersion = strideVersion;
+ });
+ }
- ///
- /// Gets the command that will set the associated version as active.
- ///
- public CommandBase SetAsActiveCommand { get; }
+ ///
+ /// Gets the command that will set the associated version as active.
+ ///
+ public CommandBase SetAsActiveCommand { get; }
- public string FullName
+ public string FullName
+ {
+ get
{
- get
- {
- return LocalPackage != null ? $"{LocalPackage.Id} {LocalPackage.Version} (installed)" : $"{ServerPackage.Id} {ServerPackage.Version}";
- }
+ return LocalPackage is not null ? $"{LocalPackage.Id} {LocalPackage.Version} (installed)" : $"{ServerPackage.Id} {ServerPackage.Version}";
}
+ }
- public PackageVersion Version => LocalPackage?.Version ?? ServerPackage.Version;
+ public PackageVersion Version => LocalPackage?.Version ?? ServerPackage.Version;
- internal void UpdateLocalPackage(NugetLocalPackage package)
- {
- OnPropertyChanging(nameof(FullName), nameof(Version));
- LocalPackage = package;
- OnPropertyChanged(nameof(FullName), nameof(Version));
- }
+ internal void UpdateLocalPackage(NugetLocalPackage package)
+ {
+ OnPropertyChanging(nameof(FullName), nameof(Version));
+ LocalPackage = package;
+ OnPropertyChanged(nameof(FullName), nameof(Version));
+ }
- internal void UpdateServerPackage(NugetServerPackage package)
- {
- OnPropertyChanging(nameof(FullName), nameof(Version));
- ServerPackage = package;
- OnPropertyChanged(nameof(FullName), nameof(Version));
- }
+ internal void UpdateServerPackage(NugetServerPackage package)
+ {
+ OnPropertyChanging(nameof(FullName), nameof(Version));
+ ServerPackage = package;
+ OnPropertyChanged(nameof(FullName), nameof(Version));
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/StrideStoreVersionViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/StrideStoreVersionViewModel.cs
index 1a25e4866f..0e7bdbc5e5 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/StrideStoreVersionViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/StrideStoreVersionViewModel.cs
@@ -1,299 +1,294 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Collections.Generic;
+
using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
using Stride.Core;
using Stride.Core.Extensions;
using Stride.Core.Packages;
using Stride.Core.Presentation.Collections;
using Stride.Core.Presentation.Services;
-using Stride.LauncherApp.Resources;
+using Stride.Launcher.Assets.Localization;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+///
+/// An implementation of the that represents an official release coming from the store.
+///
+public sealed class StrideStoreVersionViewModel : StrideVersionViewModel
{
+ public const string PrerequisitesInstaller = @"Bin\Prerequisites\install-prerequisites.exe";
+
+ internal NugetServerPackage LatestServerPackage;
+ private ReleaseNotesViewModel releaseNotes;
+
///
- /// An implementation of the that represents an official release coming from the store.
+ /// Initializes a new instance of the
///
- internal sealed class StrideStoreVersionViewModel : StrideVersionViewModel
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal StrideStoreVersionViewModel(MainViewModel launcher, NugetStore store, NugetLocalPackage? localPackage, string packageId, int major, int minor)
+ : base(launcher, store, localPackage, packageId, major, minor)
{
- public const string PrerequisitesInstaller = @"Bin\Prerequisites\install-prerequisites.exe";
-
- internal NugetServerPackage LatestServerPackage;
- private ReleaseNotesViewModel releaseNotes;
-
- ///
- /// Initializes a new instance of the
- ///
- ///
- ///
- ///
- ///
- ///
- internal StrideStoreVersionViewModel(LauncherViewModel launcher, NugetStore store, NugetLocalPackage localPackage, string packageId, int major, int minor)
- : base(launcher, store, localPackage, packageId, major, minor)
- {
- FetchReleaseNotes();
- FetchDocumentation();
- }
+ FetchReleaseNotes();
+ FetchDocumentation();
+ }
- ///
- /// Checks whether the latest available package is from a remote repository (i.e. NuGet).
- ///
- public bool IsLatestPackageRemote
+ ///
+ /// Checks whether the latest available package is from a remote repository (i.e. NuGet).
+ ///
+ public bool IsLatestPackageRemote
+ {
+ get
{
- get
- {
- return (LatestServerPackage?.Source != null) && Uri.IsWellFormedUriString(LatestServerPackage.Source, UriKind.Absolute);
- }
+ return (LatestServerPackage?.Source is not null) && Uri.IsWellFormedUriString(LatestServerPackage.Source, UriKind.Absolute);
}
+ }
- ///
- /// Checks whether the latest available package is from a local repository (i.e. disk).
- ///
- public bool IsLatestPackageLocal
+ ///
+ /// Checks whether the latest available package is from a local repository (i.e. disk).
+ ///
+ public bool IsLatestPackageLocal
+ {
+ get
{
- get
- {
- return (LatestServerPackage?.Source != null) && (Directory.Exists(LatestServerPackage.Source));
- }
+ return (LatestServerPackage?.Source is not null) && (Directory.Exists(LatestServerPackage.Source));
}
+ }
- ///
- /// Gets the full name of this version, including revision number and special revision string.
- ///
- /// If this version is installed, it will use the name of the installed version. Otherwise, it will use the name of the latest version available on the server.
- public override string FullName
+ ///
+ /// Gets the full name of this version, including revision number and special revision string.
+ ///
+ /// If this version is installed, it will use the name of the installed version. Otherwise, it will use the name of the latest version available on the server.
+ public override string FullName
+ {
+ get
{
- get
- {
- var result = Version?.ToString() ?? "Unknown";
- //if (ServerPackage != null)
- // result += $" ({ServerPackage.Source})";
- return result;
- }
+ var result = Version?.ToString() ?? "Unknown";
+ //if (ServerPackage is not null)
+ // result += $" ({ServerPackage.Source})";
+ return result;
}
+ }
+
+ ///
+ /// Gets the full name of this version on the server.
+ ///
+ public string ServerVersionFullName => ServerPackage?.Version?.ToString() ?? "";
- ///
- /// Gets the full name of this version on the server.
- ///
- public string ServerVersionFullName => ServerPackage?.Version?.ToString() ?? "";
-
- public ObservableList AlternateVersions { get; } = new ObservableList();
-
- ///
- /// Gets the release notes associated to this version.
- ///
- public ReleaseNotesViewModel ReleaseNotes { get { return releaseNotes; } private set { SetValue(ref releaseNotes, value); } }
-
- ///
- /// Gets the collection of associated with this version.
- ///
- public ObservableList DocumentationPages { get; } = new ObservableList();
-
- ///
- /// Gets the full version of the local package if it exists, or the server package.
- ///
- /// The version.
- public PackageVersion Version => LocalPackage != null ? LocalPackage.Version : ServerPackage?.Version;
-
- ///
- /// Updates the local package of this version.
- ///
- /// The local package corresponding to this version.
- internal void UpdateLocalPackage(NugetLocalPackage package, IEnumerable alternateVersions)
+ public ObservableList AlternateVersions { get; } = [];
+
+ ///
+ /// Gets the release notes associated to this version.
+ ///
+ public ReleaseNotesViewModel ReleaseNotes { get { return releaseNotes; } private set { SetValue(ref releaseNotes, value); } }
+
+ ///
+ /// Gets the collection of associated with this version.
+ ///
+ public ObservableList DocumentationPages { get; } = [];
+
+ ///
+ /// Gets the full version of the local package if it exists, or the server package.
+ ///
+ /// The version.
+ public PackageVersion Version => LocalPackage is not null ? LocalPackage.Version : ServerPackage?.Version;
+
+ ///
+ /// Updates the local package of this version.
+ ///
+ /// The local package corresponding to this version.
+ internal void UpdateLocalPackage(NugetLocalPackage? package, IEnumerable? alternateVersions)
+ {
+ OnPropertyChanging(nameof(FullName), nameof(Version));
+ LocalPackage = package;
+ OnPropertyChanged(nameof(FullName), nameof(Version));
+ Dispatcher.Invoke(UpdateStatus);
+ if (alternateVersions is not null)
{
- OnPropertyChanging(nameof(FullName), nameof(Version));
- LocalPackage = package;
- OnPropertyChanged(nameof(FullName), nameof(Version));
- Dispatcher.Invoke(UpdateStatus);
- if (alternateVersions != null)
+ Dispatcher.Invoke(() =>
{
- Dispatcher.Invoke(() =>
+ UpdateAlternateVersions(alternateVersions, (alternateVersionViewModel, alternateVersion) =>
{
- UpdateAlternateVersions(alternateVersions, (alternateVersionViewModel, alternateVersion) =>
- {
- if (alternateVersion == null && alternateVersionViewModel.ServerPackage == null)
- AlternateVersions.Remove(alternateVersionViewModel);
- else
- alternateVersionViewModel.UpdateLocalPackage(alternateVersion);
- });
+ if (alternateVersion is null && alternateVersionViewModel.ServerPackage is null)
+ AlternateVersions.Remove(alternateVersionViewModel);
+ else
+ alternateVersionViewModel.UpdateLocalPackage(alternateVersion);
});
- }
- Dispatcher.Invoke(() => UpdateFrameworks());
+ });
}
+ Dispatcher.Invoke(UpdateFrameworks);
+ }
- ///
- /// Updates the server package of this version.
- ///
- /// The server package corresponding to this version.
- internal void UpdateServerPackage(NugetServerPackage package, IEnumerable alternateVersions)
- {
- OnPropertyChanging(nameof(FullName), nameof(Version));
- ServerPackage = package;
- OnPropertyChanged(nameof(FullName), nameof(Version));
-
- // Always keep track of highest version
- if (ServerPackage != null && (LatestServerPackage == null || LatestServerPackage.Version < ServerPackage.Version))
- {
- OnPropertyChanging(nameof(IsLatestPackageRemote), nameof(IsLatestPackageLocal));
- LatestServerPackage = ServerPackage;
- OnPropertyChanged(nameof(IsLatestPackageRemote), nameof(IsLatestPackageLocal));
- }
+ ///
+ /// Updates the server package of this version.
+ ///
+ /// The server package corresponding to this version.
+ internal void UpdateServerPackage(NugetServerPackage package, IEnumerable alternateVersions)
+ {
+ OnPropertyChanging(nameof(FullName), nameof(Version));
+ ServerPackage = package;
+ OnPropertyChanged(nameof(FullName), nameof(Version));
- Dispatcher.Invoke(UpdateStatus);
- if (alternateVersions != null)
- {
- Dispatcher.Invoke(() =>
- UpdateAlternateVersions(alternateVersions, (alternateVersionViewModel, alternateVersion) =>
- {
- if (alternateVersion == null && alternateVersionViewModel.LocalPackage == null)
- AlternateVersions.Remove(alternateVersionViewModel);
- else
- alternateVersionViewModel.UpdateServerPackage(alternateVersion);
- }));
- }
+ // Always keep track of highest version
+ if (ServerPackage is not null && (LatestServerPackage is null || LatestServerPackage.Version < ServerPackage.Version))
+ {
+ OnPropertyChanging(nameof(IsLatestPackageRemote), nameof(IsLatestPackageLocal));
+ LatestServerPackage = ServerPackage;
+ OnPropertyChanged(nameof(IsLatestPackageRemote), nameof(IsLatestPackageLocal));
}
- private void UpdateAlternateVersions(IEnumerable alternateVersions, Action updateAction) where T : NugetPackage
+ Dispatcher.Invoke(UpdateStatus);
+ if (alternateVersions is not null)
{
- var updatedViewModels = new HashSet();
- foreach (var alternateVersion in alternateVersions)
- {
-
- int index = AlternateVersions.IndexOf(x => x.Version == alternateVersion.Version);
- StrideStoreAlternateVersionViewModel alternateVersionViewModel;
- if (index < 0)
- {
- // If not, add it
- alternateVersionViewModel = new StrideStoreAlternateVersionViewModel(this);
- AlternateVersions.Add(alternateVersionViewModel);
- }
- else
+ Dispatcher.Invoke(() =>
+ UpdateAlternateVersions(alternateVersions, (alternateVersionViewModel, alternateVersion) =>
{
- // If yes, update it and remove it from the list of old version
- alternateVersionViewModel = AlternateVersions[index];
- }
-
- updateAction(alternateVersionViewModel, alternateVersion);
- updatedViewModels.Add(alternateVersionViewModel);
- }
-
- // Update versions that are not installed locally anymore
- foreach (var alternateVersionViewModel in AlternateVersions.Where(x => !updatedViewModels.Contains(x)).ToList())
- {
- updateAction(alternateVersionViewModel, null);
- }
+ if (alternateVersion is null && alternateVersionViewModel.LocalPackage is null)
+ AlternateVersions.Remove(alternateVersionViewModel);
+ else
+ alternateVersionViewModel.UpdateServerPackage(alternateVersion);
+ }));
}
+ }
- internal async Task RunPrerequisitesInstaller()
+ private void UpdateAlternateVersions(IEnumerable alternateVersions, Action updateAction) where T : NugetPackage
+ {
+ var updatedViewModels = new HashSet();
+ foreach (var alternateVersion in alternateVersions)
{
- // Only used for older packages
- if (ServerPackage.Version.Version >= new Version(1, 11, 2, 0))
+
+ int index = AlternateVersions.IndexOf(x => x.Version == alternateVersion.Version);
+ StrideStoreAlternateVersionViewModel alternateVersionViewModel;
+ if (index < 0)
{
- return;
+ // If not, add it
+ alternateVersionViewModel = new(this);
+ AlternateVersions.Add(alternateVersionViewModel);
}
-
- // Run prerequisites installer (if it exists)
- var prerequisitesInstaller = PrerequisitesInstaller;
- var packagePath = Store.GetInstalledPath(ServerPackage.Id, ServerPackage.Version);
- var prerequisitesInstallerPath = Path.Combine(packagePath, prerequisitesInstaller);
- if (File.Exists(prerequisitesInstallerPath))
+ else
{
- CurrentProcessStatus = Strings.ReportInstallingPrerequisites;
- var prerequisitesInstalled = false;
- while (!prerequisitesInstalled)
- {
- try
- {
- var prerequisitesInstallerProcess = Process.Start(prerequisitesInstallerPath);
- prerequisitesInstallerProcess?.WaitForExit();
- prerequisitesInstalled = true;
- }
- catch
- {
- // We'll enter this if UAC has been declined, but also if it timed out (which is a frequent case
- // if you don't stay in front of your computer during the installation.
- var result = await ServiceProvider.Get().MessageBoxAsync("The installation of prerequisites has been canceled by user or failed to run. Do you want to run it again?", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
- if (result != MessageBoxResult.Yes)
- break;
- }
- }
+ // If yes, update it and remove it from the list of old version
+ alternateVersionViewModel = AlternateVersions[index];
}
- }
- ///
- protected override string InstallErrorMessage => string.Format(Strings.ErrorInstallingVersion, ServerVersionFullName);
-
- ///
- protected override string UninstallErrorMessage => string.Format(Strings.ErrorUninstallingVersion, FullName);
+ updateAction(alternateVersionViewModel, alternateVersion);
+ updatedViewModels.Add(alternateVersionViewModel);
+ }
- ///
- protected override Task UpdateVersionsFromStore()
+ // Update versions that are not installed locally anymore
+ foreach (var alternateVersionViewModel in AlternateVersions.Where(x => !updatedViewModels.Contains(x)).ToList())
{
- return Launcher.RetrieveAllStrideVersions();
+ updateAction(alternateVersionViewModel, null);
}
+ }
- ///
- protected override void UpdateStatus()
+ internal async Task RunPrerequisitesInstaller()
+ {
+ // Only used for older packages
+ if (ServerPackage.Version.Version >= new Version(1, 11, 2, 0))
{
- base.UpdateStatus();
- OnPropertyChanging(nameof(ServerVersionFullName));
- OnPropertyChanged(nameof(ServerVersionFullName));
+ return;
}
- ///
- protected override void UpdateInstallStatus()
+ // Run prerequisites installer (if it exists)
+ var prerequisitesInstaller = PrerequisitesInstaller;
+ var packagePath = Store.GetInstalledPath(ServerPackage.Id, ServerPackage.Version);
+ var prerequisitesInstallerPath = Path.Combine(packagePath, prerequisitesInstaller);
+ if (File.Exists(prerequisitesInstallerPath))
{
- switch (CurrentProgressAction)
+ CurrentProcessStatus = Strings.ReportInstallingPrerequisites;
+ var prerequisitesInstalled = false;
+ while (!prerequisitesInstalled)
{
- case ProgressAction.Download:
- CurrentProcessStatus = string.Format(Strings.ReportDownloadingVersion, ServerVersionFullName, CurrentProgress);
- break;
- case ProgressAction.Install:
- CurrentProcessStatus = string.Format(Strings.ReportInstallingVersion, ServerVersionFullName, CurrentProgress);
- break;
- case ProgressAction.Delete:
- CurrentProcessStatus = string.Format(Strings.ReportDeletingVersion, FullName, CurrentProgress);
- break;
+ try
+ {
+ var prerequisitesInstallerProcess = Process.Start(prerequisitesInstallerPath);
+ await prerequisitesInstallerProcess?.WaitForExitAsync();
+ prerequisitesInstalled = true;
+ }
+ catch
+ {
+ // We'll enter this if UAC has been declined, but also if it timed out (which is a frequent case
+ // if you don't stay in front of your computer during the installation.
+ var result = await ServiceProvider.Get().MessageBoxAsync("The installation of prerequisites has been canceled by user or failed to run. Do you want to run it again?", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
+ if (result != MessageBoxResult.Yes)
+ break;
+ }
}
}
+ }
- ///
- protected override void BeforeDownload()
- {
- base.BeforeDownload();
- ReleaseNotes.Show();
- }
+ ///
+ protected override string InstallErrorMessage => string.Format(Strings.ErrorInstallingVersion, ServerVersionFullName);
- ///
- protected override void AfterDownload()
- {
- base.AfterDownload();
- RunPrerequisitesInstaller().Forget();
+ ///
+ protected override string UninstallErrorMessage => string.Format(Strings.ErrorUninstallingVersion, FullName);
- Launcher.ActiveVersion = this;
- }
+ ///
+ protected override Task UpdateVersionsFromStore()
+ {
+ return Launcher.RetrieveAllStrideVersions();
+ }
+
+ ///
+ protected override void UpdateStatus()
+ {
+ base.UpdateStatus();
+ OnPropertyChanging(nameof(ServerVersionFullName));
+ OnPropertyChanged(nameof(ServerVersionFullName));
+ }
- ///
- /// Fetches the documentation pages corresponding to this version from the server.
- ///
- internal async void FetchDocumentation()
+ ///
+ protected override void UpdateInstallStatus()
+ {
+ switch (CurrentProgressAction)
{
- var pages = await DocumentationPageViewModel.FetchGettingStartedPages(ServiceProvider, $"{Major}.{Minor}");
- Dispatcher.Invoke(() => { DocumentationPages.Clear(); DocumentationPages.AddRange(pages); });
+ case ProgressAction.Download:
+ CurrentProcessStatus = string.Format(Strings.ReportDownloadingVersion, ServerVersionFullName, CurrentProgress);
+ break;
+ case ProgressAction.Install:
+ CurrentProcessStatus = string.Format(Strings.ReportInstallingVersion, ServerVersionFullName, CurrentProgress);
+ break;
+ case ProgressAction.Delete:
+ CurrentProcessStatus = string.Format(Strings.ReportDeletingVersion, FullName, CurrentProgress);
+ break;
}
+ }
- internal void FetchReleaseNotes()
+ ///
+ protected override void BeforeDownload()
+ {
+ base.BeforeDownload();
+ ReleaseNotes.Show();
+ }
+
+ ///
+ protected override void AfterDownload()
+ {
+ base.AfterDownload();
+ RunPrerequisitesInstaller().Forget();
+
+ Launcher.ActiveVersion = this;
+ }
+
+ ///
+ /// Fetches the documentation pages corresponding to this version from the server.
+ ///
+ internal async void FetchDocumentation()
+ {
+ var pages = await DocumentationPageViewModel.FetchGettingStartedPages(ServiceProvider, $"{Major}.{Minor}");
+ await Dispatcher.InvokeAsync(() => { DocumentationPages.Clear(); DocumentationPages.AddRange(pages); });
+ }
+
+ internal void FetchReleaseNotes()
+ {
+ Dispatcher.Invoke(() =>
{
- Dispatcher.Invoke(() =>
- {
- ReleaseNotes = new ReleaseNotesViewModel(Launcher, $"{Major}.{Minor}");
- ReleaseNotes.FetchReleaseNotes();
- });
- }
+ ReleaseNotes = new(Launcher, $"{Major}.{Minor}");
+ ReleaseNotes.FetchReleaseNotes();
+ });
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/StrideVersionViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/StrideVersionViewModel.cs
index 562a9d1eb5..9dc6615ca2 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/StrideVersionViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/StrideVersionViewModel.cs
@@ -1,223 +1,237 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.IO;
-using System.Linq;
+
using NuGet.Frameworks;
using Stride.Core.Packages;
using Stride.Core.Presentation.Collections;
using Stride.Core.Presentation.Commands;
-using Stride.LauncherApp.Services;
+using Stride.Launcher.Services;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+///
+/// An implementation of the that represents a major version of Stride.
+///
+public abstract class StrideVersionViewModel : PackageVersionViewModel, IComparable, IComparable>
{
- ///
- /// An implementation of the that represents a major version of Stride.
- ///
- internal abstract class StrideVersionViewModel : PackageVersionViewModel, IComparable, IComparable>
+ private bool isVisible;
+ private bool canStart;
+ private string? selectedFramework;
+
+ internal StrideVersionViewModel(MainViewModel launcher, NugetStore store, NugetLocalPackage? localPackage, string packageId, int major, int minor)
+ : base(launcher, store, localPackage)
{
- public const string MainExecutables = @"lib\net472\Stride.GameStudio.exe,lib\net472\Xenko.GameStudio.exe,Bin\Windows\Xenko.GameStudio.exe,Bin\Windows-Direct3D11\Xenko.GameStudio.exe";
- private const string StrideGameStudioExe = "Stride.GameStudio.exe";
- private const string XenkoGameStudioExe = "Xenko.GameStudio.exe";
+ PackageSimpleName = packageId
+ .Replace(".GameStudio", string.Empty)
+ .Replace(".Avalonia.Desktop", string.Empty);
+ Major = major;
+ Minor = minor;
+ SetAsActiveCommand = new AnonymousCommand(ServiceProvider, () => launcher.ActiveVersion = this);
+ // Update status if the user changes whether to display beta versions.
+ launcher.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(MainViewModel.ShowBetaVersions)) UpdateStatus(); };
+ }
- private bool isVisible;
- private bool canStart;
- private string selectedFramework;
+ protected static string[] GetExecutableNames()
+ {
+ return OperatingSystem.IsWindows()
+ ? [
+ $"{GameStudioNames.StrideAvalonia}.exe",
+ $"{GameStudioNames.Stride}.exe",
+ $"{GameStudioNames.Xenko}.exe",
+ ]
+ : [$"{GameStudioNames.StrideAvalonia}.dll"];
+ }
- internal StrideVersionViewModel(LauncherViewModel launcher, NugetStore store, NugetLocalPackage localPackage, string packageId, int major, int minor)
- : base(launcher, store, localPackage)
- {
- PackageSimpleName = packageId.Replace(".GameStudio", string.Empty);
- Major = major;
- Minor = minor;
- SetAsActiveCommand = new AnonymousCommand(ServiceProvider, () => launcher.ActiveVersion = this);
- // Update status if the user changes whether to display beta versions.
- launcher.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(LauncherViewModel.ShowBetaVersions)) UpdateStatus(); };
- }
+ protected void UpdateFrameworks()
+ {
+ Frameworks.Clear();
+ if (LocalPackage is null || InstallPath is null)
+ return;
- protected void UpdateFrameworks()
+ foreach (var toplevelFolder in new[] { "tools", "lib" })
{
- Frameworks.Clear();
- if (LocalPackage == null || InstallPath == null)
- {
- return;
- }
- foreach (var toplevelFolder in new[] { "tools", "lib" })
+ var libDirectory = Path.Combine(InstallPath, toplevelFolder);
+ if (!Directory.Exists(libDirectory))
+ continue;
+
+ foreach (var frameworkPath in Directory.EnumerateDirectories(libDirectory))
{
- var libDirectory = Path.Combine(InstallPath, toplevelFolder);
- if (Directory.Exists(libDirectory))
+ foreach (var gameStudioExecutable in GetExecutableNames())
{
- foreach (var frameworkPath in Directory.EnumerateDirectories(libDirectory))
+ if (File.Exists(Path.Combine(frameworkPath, gameStudioExecutable)))
{
- if (File.Exists(Path.Combine(frameworkPath, Major >= 4 ? StrideGameStudioExe : XenkoGameStudioExe)))
- {
- Frameworks.Add(new DirectoryInfo(frameworkPath).Name);
- }
+ Frameworks.Add(new DirectoryInfo(frameworkPath).Name);
}
}
}
+ }
+ UpdateSelectedFramework();
+ }
- if (Frameworks.Count > 0)
+ internal void UpdateSelectedFramework()
+ {
+ if (Frameworks.Count > 0)
+ {
+ try
{
- try
+ // If preferred framework exists in our list, select it
+ var preferredFramework = LauncherSettings.PreferredFramework;
+ if (Frameworks.Contains(preferredFramework))
{
- // If preferred framework exists in our list, select it
- var preferredFramework = LauncherSettings.PreferredFramework;
- if (Frameworks.Contains(preferredFramework))
- SelectedFramework = preferredFramework;
- else
- {
- // Otherwise, try to find a framework of the same kind (.NET Core or .NET Framework)
- var nugetFramework = NuGetFramework.ParseFolder(preferredFramework);
- SelectedFramework =
- Frameworks.FirstOrDefault(x => NuGetFramework.ParseFolder(preferredFramework).Framework == nugetFramework.Framework)
- ?? Frameworks.First(); // otherwise fallback to first choice
- }
+ SelectedFramework = preferredFramework;
}
- catch
+ else
{
- SelectedFramework = Frameworks.First();
+ // Otherwise, try to find a framework of the same kind (.NET Core or .NET Framework)
+ var nugetFramework = NuGetFramework.ParseFolder(preferredFramework);
+ SelectedFramework =
+ Frameworks.FirstOrDefault(x => NuGetFramework.ParseFolder(preferredFramework).Framework == nugetFramework.Framework)
+ ?? Frameworks.First(); // otherwise fallback to first choice
}
}
+ catch
+ {
+ SelectedFramework = Frameworks.First();
+ }
}
+ }
- public string PackageSimpleName { get; }
-
- ///
- /// Gets the major number of this version.
- ///
- public int Major { get; }
-
- ///
- /// Gets the minor number of this version.
- ///
- public int Minor { get; }
-
- ///
- /// Gets the name of this version.
- ///
- public override string Name => GetName(PackageSimpleName, Major, Minor);
-
- ///
- /// Gets the display name of this version.
- ///
- public virtual string DisplayName => GetName(PackageSimpleName, Major, Minor, true);
-
- ///
- /// Gets the command that will set the associated package as active.
- ///
- public CommandBase SetAsActiveCommand { get; }
-
- ///
- /// Gets whether this version is a beta version.
- ///
- public bool IsBeta => IsBetaVersion(Major, Minor);
-
- ///
- /// Gets whether this version should be displayed.
- ///
- public bool IsVisible { get { return isVisible; } private set { SetValue(ref isVisible, value); } }
-
- ///
- /// Gets whether this version can be started.
- ///
- public bool CanStart { get { return canStart; } private set { SetValue(ref canStart, value); } }
-
- public ObservableList Frameworks { get; } = new ObservableList();
-
- public string SelectedFramework { get { return selectedFramework; } set { SetValue(ref selectedFramework, value); } }
-
- ///
- /// Builds a string that represents the given version numbers.
- ///
- /// The major version number.
- /// The minor version number.
- /// Indicates whether the name to compute is a display name, or a string token used to build urls.
- /// A string representing the given version numbers.
- public static string GetName(string packageSimpleName, int majorVersion, int minorVersion, bool isDisplayName = false)
- {
- if (isDisplayName && IsBetaVersion(majorVersion, minorVersion))
- return $"{packageSimpleName} {majorVersion}.{minorVersion}-beta";
+ public string PackageSimpleName { get; }
- return $"{packageSimpleName} {majorVersion}.{minorVersion}";
- }
+ ///
+ /// Gets the major number of this version.
+ ///
+ public int Major { get; }
- ///
- /// Indicates if the given version corresponds to a beta version.
- ///
- /// The major number of the version.
- /// The minor nimber of the version.
- /// True if the given version is a beta, false otherwise.
- public static bool IsBetaVersion(int majorVersion, int minorVersion)
- {
- return majorVersion < 3;
- }
+ ///
+ /// Gets the minor number of this version.
+ ///
+ public int Minor { get; }
- ///
- protected override void UpdateStatus()
- {
- base.UpdateStatus();
- // It is visible if it's installed, or if it's not a beta, or if user want to see be available betas
- IsVisible = Launcher.ShowBetaVersions || !IsBeta || CanDelete;
- SetAsActiveCommand.IsEnabled = CanDelete;
- DeleteCommand.IsEnabled = CanDelete;
- CanStart = CanDelete;
-
- if (Launcher.ActiveVersion == this)
- Launcher.StartStudioCommand.IsEnabled = CanStart;
- }
+ ///
+ /// Gets the name of this version.
+ ///
+ public override string Name => GetName(PackageSimpleName, Major, Minor);
- ///
- /// Name of main executable of current store.
- ///
- /// Name of the executable.
- public string GetMainExecutables()
- {
- return MainExecutables;
- }
+ ///
+ /// Gets the display name of this version.
+ ///
+ public virtual string DisplayName => GetName(PackageSimpleName, Major, Minor, true);
+
+ ///
+ /// Gets the command that will set the associated package as active.
+ ///
+ public CommandBase SetAsActiveCommand { get; }
- ///
- /// Locate the main executable from a given package installation path. It throws exceptions if not found.
- ///
- /// The package installation path.
- /// The main executable.
- public string LocateMainExecutable()
+ ///
+ /// Gets whether this version is a beta version.
+ ///
+ public bool IsBeta => IsBetaVersion(Major, Minor);
+
+ ///
+ /// Gets whether this version should be displayed.
+ ///
+ public bool IsVisible { get { return isVisible; } private set { SetValue(ref isVisible, value); } }
+
+ ///
+ /// Gets whether this version can be started.
+ ///
+ public bool CanStart { get { return canStart; } private set { SetValue(ref canStart, value); } }
+
+ public ObservableList Frameworks { get; } = [];
+
+ public string? SelectedFramework { get { return selectedFramework; } set { SetValue(ref selectedFramework, value); } }
+
+ ///
+ /// Builds a string that represents the given version numbers.
+ ///
+ /// The major version number.
+ /// The minor version number.
+ /// Indicates whether the name to compute is a display name, or a string token used to build urls.
+ /// A string representing the given version numbers.
+ public static string GetName(string packageSimpleName, int majorVersion, int minorVersion, bool isDisplayName = false)
+ {
+ if (isDisplayName && IsBetaVersion(majorVersion, minorVersion))
+ return $"{packageSimpleName} {majorVersion}.{minorVersion}-beta";
+
+ return $"{packageSimpleName} {majorVersion}.{minorVersion}";
+ }
+
+ ///
+ /// Indicates if the given version corresponds to a beta version.
+ ///
+ /// The major number of the version.
+ /// The minor nimber of the version.
+ /// True if the given version is a beta, false otherwise.
+ public static bool IsBetaVersion(int majorVersion, int minorVersion)
+ {
+ return majorVersion < 3;
+ }
+
+ ///
+ protected override void UpdateStatus()
+ {
+ base.UpdateStatus();
+ // It is visible if it's installed, or if it's not a beta, or if user want to see be available betas
+ IsVisible = Launcher.ShowBetaVersions || !IsBeta || CanDelete;
+ SetAsActiveCommand.IsEnabled = CanDelete;
+ DeleteCommand.IsEnabled = CanDelete;
+ CanStart = CanDelete;
+
+ if (Launcher.ActiveVersion == this)
+ Launcher.StartStudioCommand.IsEnabled = CanStart;
+ }
+
+ ///
+ /// Locate the main executable from a given package installation path. It throws exceptions if not found.
+ ///
+ /// The main executable.
+ public string? LocateMainExecutable()
+ {
+ if (InstallPath is null)
+ return null;
+
+ // First, try to use the selected framework
+ if (SelectedFramework is not null)
{
- // First, try to use the selected framework
- if (SelectedFramework != null)
+ foreach (var toplevelFolder in new[] { "tools", "lib" })
{
- foreach (var toplevelFolder in new[] { "tools", "lib" })
+ var gameStudioDirectory = Path.Combine(InstallPath, toplevelFolder, SelectedFramework);
+ foreach (var gameStudioExecutable in GetExecutableNames())
{
- var gameStudioDirectory = Path.Combine(InstallPath, toplevelFolder, SelectedFramework);
- foreach (var gameStudioExecutable in new[] { "Stride.GameStudio.exe", "Xenko.GameStudio.exe" })
- {
- var gameStudioPath = Path.Combine(gameStudioDirectory, gameStudioExecutable);
- if (File.Exists(gameStudioPath))
- return gameStudioPath;
- }
+ var gameStudioPath = Path.Combine(gameStudioDirectory, gameStudioExecutable);
+ if (File.Exists(gameStudioPath))
+ return gameStudioPath;
}
}
-
- // Otherwise, old-style fallback
- var mainExecutableList = GetMainExecutables();
- var fullExePath = mainExecutableList.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Path.Combine(InstallPath, x)).FirstOrDefault(File.Exists);
- if (fullExePath == null)
- throw new InvalidOperationException("Unable to locate the executable for the selected version");
-
- return fullExePath;
}
+ // Otherwise, old-style fallback
+ return GetMainExecutables().Select(x => Path.Combine(InstallPath, x)).FirstOrDefault(File.Exists)
+ ?? throw new InvalidOperationException("Unable to locate the executable for the selected version");
- public int CompareTo(StrideVersionViewModel other)
+ static IEnumerable GetMainExecutables()
{
- var r = Major.CompareTo(other.Major);
- return r != 0 ? -r : -Minor.CompareTo(other.Minor);
+ // some old paths used in previous versions
+ if (OperatingSystem.IsWindows())
+ {
+ yield return @$"lib\net472\{GameStudioNames.Stride}.exe";
+ yield return @$"lib\net472\{GameStudioNames.Xenko}.exe";
+ yield return @$"Bin\Windows\{GameStudioNames.Xenko}.exe";
+ yield return @$"Bin\Windows-Direct3D11\{GameStudioNames.Xenko}.exe";
+ }
}
+ }
- public int CompareTo(Tuple other)
- {
- var r = Major.CompareTo(other.Item1);
- return r != 0 ? -r : -Minor.CompareTo(other.Item2);
- }
+ public int CompareTo(StrideVersionViewModel? other)
+ {
+ var r = Major.CompareTo(other?.Major);
+ return r != 0 ? -r : -Minor.CompareTo(other?.Minor);
+ }
+
+ public int CompareTo(Tuple? other)
+ {
+ var r = Major.CompareTo(other?.Item1);
+ return r != 0 ? -r : -Minor.CompareTo(other?.Item2);
}
}
diff --git a/sources/launcher/Stride.Launcher/ViewModels/VsixVersionViewModel.cs b/sources/launcher/Stride.Launcher/ViewModels/VsixVersionViewModel.cs
index 852cb7b53f..2bdedf21c3 100644
--- a/sources/launcher/Stride.Launcher/ViewModels/VsixVersionViewModel.cs
+++ b/sources/launcher/Stride.Launcher/ViewModels/VsixVersionViewModel.cs
@@ -1,150 +1,146 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
+
using Stride.Core.Extensions;
using Stride.Core.Packages;
using Stride.Core.Presentation.Commands;
using Stride.Core.Presentation.Services;
-using Stride.LauncherApp.Resources;
+using Stride.Launcher.Assets.Localization;
+
+namespace Stride.Launcher.ViewModels;
-namespace Stride.LauncherApp.ViewModels
+public sealed class VsixVersionViewModel : PackageVersionViewModel
{
- internal sealed class VsixVersionViewModel : PackageVersionViewModel
+ private readonly string packageId;
+ private bool isLatestVersionInstalled;
+ private string status;
+ private readonly NugetStore.VsixSupportedVsVersion vsixSupportedVsVersion;
+
+ internal VsixVersionViewModel(MainViewModel launcher, NugetStore store, string packageId, NugetStore.VsixSupportedVsVersion vsixSupportedVsVersion)
+ : base(launcher, store, null)
{
- private readonly string packageId;
- private bool isLatestVersionInstalled;
- private string status;
- private readonly NugetStore.VsixSupportedVsVersion vsixSupportedVsVersion;
+ this.packageId = packageId;
+ this.vsixSupportedVsVersion = vsixSupportedVsVersion;
+ status = FormatStatus(Strings.ReportChecking);
+ ExecuteActionCommand = new AnonymousTaskCommand(ServiceProvider, ExecuteAction) { IsEnabled = false };
+ }
- internal VsixVersionViewModel(LauncherViewModel launcher, NugetStore store, string packageId, NugetStore.VsixSupportedVsVersion vsixSupportedVsVersion)
- : base(launcher, store, null)
- {
- this.packageId = packageId;
- this.vsixSupportedVsVersion = vsixSupportedVsVersion;
- status = FormatStatus(Strings.ReportChecking);
- ExecuteActionCommand = new AnonymousTaskCommand(ServiceProvider, ExecuteAction) { IsEnabled = false };
- }
+ ///
+ public override string Name => Strings.VisualStudioExtension;
- ///
- public override string Name => Strings.VisualStudioExtension;
+ ///
+ public override string FullName => Name;
- ///
- public override string FullName => Name;
+ ///
+ /// Gets whether the latest version of the VSIX package is installed.
+ ///
+ /// This property is updated by and requires the latest Nuget package to be in the local store.
+ public bool IsLatestVersionInstalled { get { return isLatestVersionInstalled; } private set { SetValue(ref isLatestVersionInstalled, value); } }
- ///
- /// Gets whether the latest version of the VSIX package is installed.
- ///
- /// This property is updated by and requires the latest Nuget package to be in the local store.
- public bool IsLatestVersionInstalled { get { return isLatestVersionInstalled; } private set { SetValue(ref isLatestVersionInstalled, value); } }
+ ///
+ /// Gets the current status of the VSIX package.
+ ///
+ public string Status { get { return status; } private set { SetValue(ref status, value); } }
- ///
- /// Gets the current status of the VSIX package.
- ///
- public string Status { get { return status; } private set { SetValue(ref status, value); } }
+ ///
+ /// Gets a command that will download the latest version of the VSIX and install it on all compatible versions of Visual Studio.
+ ///
+ public ICommandBase ExecuteActionCommand { get; }
- ///
- /// Gets a command that will download the latest version of the VSIX and install it on all compatible versions of Visual Studio.
- ///
- public ICommandBase ExecuteActionCommand { get; }
+ ///
+ protected override string InstallErrorMessage => Strings.ErrorInstallingVSIX;
- ///
- protected override string InstallErrorMessage => Strings.ErrorInstallingVSIX;
+ ///
+ protected override string UninstallErrorMessage => Strings.ErrorUninstallingVSIX;
- ///
- protected override string UninstallErrorMessage => Strings.ErrorUninstallingVSIX;
+ public async Task UpdateFromStore()
+ {
+ Dispatcher.Invoke(() => Status = FormatStatus(Strings.ReportChecking));
+ await UpdateVersionsFromStore();
+ await Dispatcher.InvokeAsync(UpdateStatus);
+ }
- public async Task UpdateFromStore()
+ ///
+ protected override void UpdateStatus()
+ {
+ base.UpdateStatus();
+ var newStatus = Strings.VSIXVerbReinstall;
+ if (CanBeDownloaded)
{
- Dispatcher.Invoke(() => Status = FormatStatus(Strings.ReportChecking));
- await UpdateVersionsFromStore();
- Dispatcher.Invoke(UpdateStatus);
+ newStatus = LocalPackage is null ? Strings.VSIXVerbInstall : Strings.VSIXVerbUpdate;
+ IsLatestVersionInstalled = false;
}
- ///
- protected override void UpdateStatus()
- {
- base.UpdateStatus();
- var newStatus = Strings.VSIXVerbReinstall;
- if (CanBeDownloaded)
- {
- newStatus = LocalPackage == null ? Strings.VSIXVerbInstall : Strings.VSIXVerbUpdate;
- IsLatestVersionInstalled = false;
- }
-
- // Enable the control only if there is an eligible package for the VS extension.
- ExecuteActionCommand.IsEnabled = (LocalPackage != null || ServerPackage != null);
- Status = FormatStatus(newStatus);
- }
+ // Enable the control only if there is an eligible package for the VS extension.
+ ExecuteActionCommand.IsEnabled = (LocalPackage is not null || ServerPackage is not null);
+ Status = FormatStatus(newStatus);
+ }
- private string FormatStatus(string status)
+ private string FormatStatus(string status)
+ {
+ string vsixTarget = "Visual Studio {0} extension";
+ switch (vsixSupportedVsVersion)
{
- string vsixTarget = "Visual Studio {0} extension";
- switch (vsixSupportedVsVersion)
- {
- case NugetStore.VsixSupportedVsVersion.VS2019:
- vsixTarget = string.Format(vsixTarget, "2019");
- break;
- case NugetStore.VsixSupportedVsVersion.VS2022AndNext:
- vsixTarget = string.Format(vsixTarget, "2022+");
- break;
- }
- return $"{vsixTarget}: {status}";
+ case NugetStore.VsixSupportedVsVersion.VS2019:
+ vsixTarget = string.Format(vsixTarget, "2019");
+ break;
+ case NugetStore.VsixSupportedVsVersion.VS2022AndNext:
+ vsixTarget = string.Format(vsixTarget, "2022+");
+ break;
}
+ return $"{vsixTarget}: {status}";
+ }
- ///
- protected override void UpdateInstallStatus()
+ ///
+ protected override void UpdateInstallStatus()
+ {
+ switch (CurrentProgressAction)
{
- switch (CurrentProgressAction)
- {
- case ProgressAction.Download:
- CurrentProcessStatus = string.Format(Strings.ReportDownloadingVSIX, CurrentProgress);
- break;
- case ProgressAction.Install:
- CurrentProcessStatus = string.Format(Strings.ReportInstallingVSIX, CurrentProgress);
- break;
- case ProgressAction.Delete:
- CurrentProcessStatus = string.Format(Strings.ReportDeletingVersion, FullName, CurrentProgress);
- break;
- }
+ case ProgressAction.Download:
+ CurrentProcessStatus = string.Format(Strings.ReportDownloadingVSIX, CurrentProgress);
+ break;
+ case ProgressAction.Install:
+ CurrentProcessStatus = string.Format(Strings.ReportInstallingVSIX, CurrentProgress);
+ break;
+ case ProgressAction.Delete:
+ CurrentProcessStatus = string.Format(Strings.ReportDeletingVersion, FullName, CurrentProgress);
+ break;
}
+ }
- ///
- protected override async Task UpdateVersionsFromStore()
- {
- var versionRange = Store.VsixVersionToStrideRelease[vsixSupportedVsVersion];
- var minVersion = versionRange.MinVersion;
- var maxVersion = versionRange.MaxVersion;
+ ///
+ protected override async Task UpdateVersionsFromStore()
+ {
+ var versionRange = Store.VsixVersionToStrideRelease[vsixSupportedVsVersion];
+ var minVersion = versionRange.MinVersion;
+ var maxVersion = versionRange.MaxVersion;
- LocalPackage = await Launcher.RunLockTask(() => Store.GetLocalPackages(packageId).Where(package => package.Version >= minVersion && package.Version < maxVersion).OrderByDescending(p => p.Version).FirstOrDefault());
- ServerPackage = await Launcher.RunLockTask(() => Store.FindSourcePackagesById(packageId, CancellationToken.None).Result.Where(package => package.Version >= minVersion && package.Version < maxVersion).OrderByDescending(p => p.Version).FirstOrDefault());
- }
+ LocalPackage = await Launcher.RunLockTask(() => Store.GetLocalPackages(packageId).Where(package => package.Version >= minVersion && package.Version < maxVersion).OrderByDescending(p => p.Version).FirstOrDefault());
+ ServerPackage = await Launcher.RunLockTask(() => Store.FindSourcePackagesById(packageId, CancellationToken.None).Result.Where(package => package.Version >= minVersion && package.Version < maxVersion).OrderByDescending(p => p.Version).FirstOrDefault());
+ }
- public async Task ExecuteAction()
+ public async Task ExecuteAction()
+ {
+ await Task.Run(async () =>
{
- await Task.Run(async () =>
+ await Download(false);
+
+ IsProcessing = true;
+ string checkingStatus = Strings.ReportChecking;
+ try
{
- await Download(false);
-
- IsProcessing = true;
- string checkingStatus = Strings.ReportChecking;
- try
- {
- CurrentProcessStatus = checkingStatus;
- IsProcessing = false;
- await ServiceProvider.Get().MessageBoxAsync(Strings.VSIXInstallSucessful, MessageBoxButton.OK, MessageBoxImage.Information);
- }
- catch (Exception e)
- {
- CurrentProcessStatus = checkingStatus;
- IsProcessing = false;
- var message = $"{Strings.ErrorInstallingVSIX}{e.FormatSummary(true)}";
- await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
- }
- UpdateStatus();
- });
- }
+ CurrentProcessStatus = checkingStatus;
+ IsProcessing = false;
+ await ServiceProvider.Get().MessageBoxAsync(Strings.VSIXInstallSucessful, MessageBoxButton.OK, MessageBoxImage.Information);
+ }
+ catch (Exception e)
+ {
+ CurrentProcessStatus = checkingStatus;
+ IsProcessing = false;
+ var message = $"{Strings.ErrorInstallingVSIX}{e.FormatSummary(true)}";
+ await ServiceProvider.Get().MessageBoxAsync(message, MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ UpdateStatus();
+ });
}
}
diff --git a/sources/launcher/Stride.Launcher/Views/Announcement.axaml b/sources/launcher/Stride.Launcher/Views/Announcement.axaml
new file mode 100644
index 0000000000..074b03bc2c
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Views/Announcement.axaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sources/launcher/Stride.Launcher/Views/Announcement.axaml.cs b/sources/launcher/Stride.Launcher/Views/Announcement.axaml.cs
new file mode 100644
index 0000000000..209085644e
--- /dev/null
+++ b/sources/launcher/Stride.Launcher/Views/Announcement.axaml.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
+// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
+
+using Avalonia.Controls;
+
+namespace Stride.Launcher.Views;
+
+public partial class Announcement : UserControl
+{
+ public Announcement()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/sources/launcher/Stride.Launcher/Views/Announcement.xaml b/sources/launcher/Stride.Launcher/Views/Announcement.xaml
deleted file mode 100644
index 24eb182b9b..0000000000
--- a/sources/launcher/Stride.Launcher/Views/Announcement.xaml
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
- 40
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sources/launcher/Stride.Launcher/Views/Announcement.xaml.cs b/sources/launcher/Stride.Launcher/Views/Announcement.xaml.cs
deleted file mode 100644
index 72f866174e..0000000000
--- a/sources/launcher/Stride.Launcher/Views/Announcement.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System.Windows.Controls;
-
-namespace Stride.LauncherApp.Views
-{
- ///
- /// Interaction logic for Announcement.xaml
- ///
- public partial class Announcement : UserControl
- {
- public Announcement()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/sources/launcher/Stride.Launcher/Views/Commands.cs b/sources/launcher/Stride.Launcher/Views/Commands.cs
deleted file mode 100644
index 494341688e..0000000000
--- a/sources/launcher/Stride.Launcher/Views/Commands.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
-// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
-using System;
-using System.Diagnostics;
-using System.Windows;
-using System.Windows.Threading;
-using Stride.Core.Annotations;
-using Stride.Core.Presentation.Commands;
-using Stride.Core.Presentation.Extensions;
-using Stride.Core.Presentation.View;
-using Stride.Core.Presentation.ViewModels;
-
-namespace Stride.LauncherApp.Views
-{
- public static class Commands
- {
- private static readonly Lazy LazyOpenHyperlinkCommand = new Lazy(OpenHyperlinkCommandFactory);
-
- public static ICommandBase OpenHyperlinkCommand => LazyOpenHyperlinkCommand.Value;
-
- [NotNull]
- private static ICommandBase OpenHyperlinkCommandFactory()
- {
- // TODO: have a proper way to initialize the services (maybe at application startup)
- var serviceProvider = new ViewModelServiceProvider(new[] { new DispatcherService(Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher) });
- return new AnonymousCommand(serviceProvider, OpenHyperlink, CanOpenHyperlink);
- }
-
- private static bool CanOpenHyperlink([CanBeNull] string url)
- {
- return !string.IsNullOrEmpty(url) && Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute);
- }
-
- private static void OpenHyperlink([NotNull] string url)
- {
- // see https://support.microsoft.com/en-us/kb/305703
- try
- {
- // Make sure we open proper HTML pages
- Process.Start(new ProcessStartInfo(url.ReplaceLast(".md", ".html")) { UseShellExecute = true });
- }
- catch (System.ComponentModel.Win32Exception e)
- {
- if (e.ErrorCode == -2147467259)
- MessageBox.Show(e.Message);
- }
- catch (Exception e)
- {
- MessageBox.Show(e.Message);
- }
- }
- }
-}
diff --git a/sources/launcher/Stride.Launcher/Views/LauncherWindow.xaml b/sources/launcher/Stride.Launcher/Views/LauncherWindow.xaml
deleted file mode 100644
index 3fe2ff3f03..0000000000
--- a/sources/launcher/Stride.Launcher/Views/LauncherWindow.xaml
+++ /dev/null
@@ -1,808 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 20
- 12
- 0,0,0,20
-
- 0,15,0,31
-
-
-
- #DA0830
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-