From 0768457380a5e0d94ffc43cc37aedc873da0efbb Mon Sep 17 00:00:00 2001 From: Jung Hyun Nam Date: Mon, 15 Jan 2024 18:08:18 +0900 Subject: [PATCH] =?UTF-8?q?Hostess:=20MainWindowInstallPackagesCommand?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Steps=20Player=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Hostess/App.xaml.cs | 3 +- .../MainWindowInstallPackagesCommand.cs | 220 +----------------- src/Hostess/Components/IStepsPlayer.cs | 9 +- .../Implementations/StepsComposer.cs | 12 +- .../Components/Implementations/StepsPlayer.cs | 220 ++++++++++++++++++ src/Hostess/Hostess.csproj | 1 + 6 files changed, 246 insertions(+), 219 deletions(-) create mode 100644 src/Hostess/Components/Implementations/StepsPlayer.cs diff --git a/src/Hostess/App.xaml.cs b/src/Hostess/App.xaml.cs index f3e6e5c..292e9d2 100644 --- a/src/Hostess/App.xaml.cs +++ b/src/Hostess/App.xaml.cs @@ -108,7 +108,8 @@ private void ConfigureServices(IServiceCollection services) .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); // Shared Commands services diff --git a/src/Hostess/Commands/MainWindow/MainWindowInstallPackagesCommand.cs b/src/Hostess/Commands/MainWindow/MainWindowInstallPackagesCommand.cs index 3422901..0c521e0 100644 --- a/src/Hostess/Commands/MainWindow/MainWindowInstallPackagesCommand.cs +++ b/src/Hostess/Commands/MainWindow/MainWindowInstallPackagesCommand.cs @@ -1,231 +1,29 @@ using Hostess.Components; using Hostess.ViewModels; -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using TableCloth; -using TableCloth.Resources; namespace Hostess.Commands.MainWindow { public sealed class MainWindowInstallPackagesCommand : ViewModelCommandBase { public MainWindowInstallPackagesCommand( - IResourceCacheManager resourceCacheManager, - IAppMessageBox appMessageBox, - ISharedLocations sharedLocations, - ICommandLineArguments commandLineArguments) + IStepsPlayer stepsPlayer) { - _resourceCacheManager = resourceCacheManager; - _appMessageBox = appMessageBox; - _sharedLocations = sharedLocations; - _commandLineArguments = commandLineArguments; + _stepsPlayer = stepsPlayer; } - private readonly IResourceCacheManager _resourceCacheManager; - private readonly IAppMessageBox _appMessageBox; - private readonly ISharedLocations _sharedLocations; - private readonly ICommandLineArguments _commandLineArguments; - - private bool _isRunning = false; + private readonly IStepsPlayer _stepsPlayer; protected override bool EvaluateCanExecute() - => !_isRunning; + => !_stepsPlayer.IsRunning; public override async void Execute(MainWindowViewModel viewModel) { - try - { - _isRunning = true; - var hasAnyFailure = false; - var catalog = _resourceCacheManager.CatalogDocument; - - foreach (InstallItemViewModel eachItem in viewModel.InstallItems) - { - try - { - if (eachItem.InstallItemType == InstallItemType.DownloadAndInstall) - await ProcessDownloadAndInstallAsync(eachItem); - else if (eachItem.InstallItemType == InstallItemType.PowerShellScript) - await ProcessPowerShellScriptAsync(eachItem); - else if (eachItem.InstallItemType == InstallItemType.OpenWebSite) - await OpenAddInWebSiteAsync(eachItem); - else if (eachItem.InstallItemType == InstallItemType.CustomAction) - await eachItem.CustomAction?.Invoke(eachItem); - - eachItem.StatusMessage = UIStringResources.Hostess_Install_Succeed; - eachItem.Installed = true; - eachItem.ErrorMessage = null; - } - catch (Exception ex) - { - hasAnyFailure = true; - eachItem.StatusMessage = UIStringResources.Hostess_Install_Failed; - eachItem.Installed = false; - eachItem.ErrorMessage = ex is AggregateException exception ? exception.InnerException.Message : ex.Message; - await Task.Delay(100); - } - } - - if (!hasAnyFailure) - { - var parsedArgs = _commandLineArguments.Current; - var targets = parsedArgs.SelectedServices; - - foreach (var eachUrl in catalog.Services.Where(x => targets.Contains(x.Id)).Select(x => x.Url)) - await OpenRequestedWebSite(eachUrl); - - viewModel.RequestClose(this); - return; - } - } - catch (Exception ex) - { - _appMessageBox.DisplayError(ex, true); - } - finally - { - _isRunning = false; - } - } - - private async Task ProcessDownloadAndInstallAsync(InstallItemViewModel eachItem) - { - var parsedArgs = _commandLineArguments.Current; - - eachItem.Installed = null; - eachItem.StatusMessage = UIStringResources.Hostess_Download_InProgress; - - var downloadFolderPath = _sharedLocations.GetDownloadDirectoryPath(); - var tempFileName = $"installer_{Guid.NewGuid():n}.exe"; - var tempFilePath = System.IO.Path.Combine(downloadFolderPath, tempFileName); - - if (File.Exists(tempFilePath)) - File.Delete(tempFilePath); - - using (var webClient = new WebClient()) - { - webClient.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml"); - webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko"); - await webClient.DownloadFileTaskAsync(eachItem.PackageUrl, tempFilePath).ConfigureAwait(false); - - eachItem.StatusMessage = UIStringResources.Hostess_Install_InProgress; - - if (parsedArgs.DryRun) - { - await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); - return; - } - - var psi = new ProcessStartInfo(tempFilePath, eachItem.Arguments) - { - UseShellExecute = false, - }; - - var cpSource = new TaskCompletionSource(); - using (var process = new Process() { StartInfo = psi, }) - { - process.EnableRaisingEvents = true; - process.Exited += (_sender, _e) => - { - var realSender = _sender as Process; - cpSource.SetResult(realSender.ExitCode); - }; - - if (!process.Start()) - throw new ApplicationException(ErrorStrings.Error_Package_CanNotStart); - - await cpSource.Task.ConfigureAwait(false); - } - } - } - - private async Task ProcessPowerShellScriptAsync(InstallItemViewModel eachItem) - { - var parsedArgs = _commandLineArguments.Current; - - eachItem.Installed = null; - eachItem.StatusMessage = UIStringResources.Hostess_Install_InProgress; - - var downloadFolderPath = _sharedLocations.GetDownloadDirectoryPath(); - var tempFileName = $"bootstrap_{Guid.NewGuid():n}.ps1"; - var tempFilePath = System.IO.Path.Combine(downloadFolderPath, tempFileName); - - if (File.Exists(tempFilePath)) - File.Delete(tempFilePath); - - File.WriteAllText(tempFilePath, eachItem.ScriptContent, Encoding.Unicode); - var powershellPath = _sharedLocations.GetDefaultPowerShellExecutableFilePath(); - - if (!File.Exists(powershellPath)) - throw new Exception(ErrorStrings.Error_No_WindowsPowerShell); - - if (parsedArgs.DryRun) - { - await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); - return; - } - - var psi = new ProcessStartInfo(powershellPath, $"Set-ExecutionPolicy Bypass -Scope Process -Force; {tempFilePath}") - { - UseShellExecute = false, - }; - - var cpSource = new TaskCompletionSource(); - using (var process = new Process() { StartInfo = psi, }) - { - process.EnableRaisingEvents = true; - process.Exited += (_sender, _e) => - { - var realSender = _sender as Process; - cpSource.SetResult(realSender.ExitCode); - }; - - if (!process.Start()) - throw new ApplicationException(ErrorStrings.Error_Package_CanNotStart); - - await cpSource.Task.ConfigureAwait(false); - } - } - - private async Task OpenAddInWebSiteAsync(InstallItemViewModel viewModel) - { - var parsedArgs = _commandLineArguments.Current; - - if (parsedArgs.DryRun) - { - await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); - return; - } - - Process.Start(new ProcessStartInfo(viewModel.PackageUrl) - { - UseShellExecute = true, - WindowStyle = ProcessWindowStyle.Maximized, - }); - } - - private async Task OpenRequestedWebSite(string targetUrl) - { - var parsedArgs = _commandLineArguments.Current; - - if (parsedArgs.DryRun) - { - await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); - return; - } + var hasAnyFailure = await _stepsPlayer.PlayStepsAsync( + viewModel.InstallItems, + viewModel.ShowDryRunNotification); - Process.Start(new ProcessStartInfo(targetUrl) - { - UseShellExecute = true, - WindowStyle = ProcessWindowStyle.Maximized, - }); + if (!hasAnyFailure) + viewModel.RequestClose(this); } } } diff --git a/src/Hostess/Components/IStepsPlayer.cs b/src/Hostess/Components/IStepsPlayer.cs index ce2ccb3..efdb93c 100644 --- a/src/Hostess/Components/IStepsPlayer.cs +++ b/src/Hostess/Components/IStepsPlayer.cs @@ -1,10 +1,17 @@ using Hostess.ViewModels; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Hostess.Components { public interface IStepsPlayer { - bool PlaySteps(IEnumerable composedSteps); + bool IsRunning { get; } + + Task PlayStepsAsync( + IEnumerable composedSteps, + bool dryRun, + CancellationToken cancellationToken = default); } } \ No newline at end of file diff --git a/src/Hostess/Components/Implementations/StepsComposer.cs b/src/Hostess/Components/Implementations/StepsComposer.cs index 3134974..5bf1c60 100644 --- a/src/Hostess/Components/Implementations/StepsComposer.cs +++ b/src/Hostess/Components/Implementations/StepsComposer.cs @@ -56,7 +56,7 @@ public IEnumerable ComposeSteps() InstallItemType = InstallItemType.CustomAction, TargetSiteName = UIStringResources.Option_Prerequisites, PackageName = UIStringResources.Install_VerifyEnvironment, - CustomAction = VerifyWindowsContainerEnvironment, + CustomAction = VerifyWindowsContainerEnvironmentAsync, }, new InstallItemViewModel() { @@ -70,14 +70,14 @@ public IEnumerable ComposeSteps() InstallItemType = InstallItemType.CustomAction, TargetSiteName = UIStringResources.Option_Prerequisites, PackageName = UIStringResources.Install_TryProtectCriticalServices, - CustomAction = TryProtectCriticalServices, + CustomAction = TryProtectCriticalServicesAsync, }, new InstallItemViewModel() { InstallItemType = InstallItemType.CustomAction, TargetSiteName = UIStringResources.Option_Prerequisites, PackageName = UIStringResources.Install_SetDesktopWallpaper, - CustomAction = SetDesktopWallpaper, + CustomAction = SetDesktopWallpaperAsync, }, }; @@ -243,7 +243,7 @@ private async Task EnableIEModeAsync(InstallItemViewModel viewModel) } } - private async Task VerifyWindowsContainerEnvironment(InstallItemViewModel viewModel) + private async Task VerifyWindowsContainerEnvironmentAsync(InstallItemViewModel viewModel) { var parsedArgs = _commandLineArguments.Current; @@ -264,7 +264,7 @@ private async Task VerifyWindowsContainerEnvironment(InstallItemViewModel viewMo } } - private async Task TryProtectCriticalServices(InstallItemViewModel viewModel) + private async Task TryProtectCriticalServicesAsync(InstallItemViewModel viewModel) { var parsedArgs = _commandLineArguments.Current; @@ -283,7 +283,7 @@ private async Task TryProtectCriticalServices(InstallItemViewModel viewModel) catch (Exception ex) { _appMessageBox.DisplayError(ex, false); } } - private async Task SetDesktopWallpaper(InstallItemViewModel viewModel) + private async Task SetDesktopWallpaperAsync(InstallItemViewModel viewModel) { var parsedArgs = _commandLineArguments.Current; diff --git a/src/Hostess/Components/Implementations/StepsPlayer.cs b/src/Hostess/Components/Implementations/StepsPlayer.cs new file mode 100644 index 0000000..873a70a --- /dev/null +++ b/src/Hostess/Components/Implementations/StepsPlayer.cs @@ -0,0 +1,220 @@ +using Hostess.ViewModels; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using TableCloth.Resources; + +namespace Hostess.Components.Implementations +{ + public sealed class StepsPlayer : IStepsPlayer + { + public StepsPlayer( + IResourceCacheManager resourceCacheManager, + IAppMessageBox appMessageBox, + ISharedLocations sharedLocations, + ICommandLineArguments commandLineArguments) + { + _resourceCacheManager = resourceCacheManager; + _appMessageBox = appMessageBox; + _sharedLocations = sharedLocations; + _commandLineArguments = commandLineArguments; + } + + private readonly IResourceCacheManager _resourceCacheManager; + private readonly IAppMessageBox _appMessageBox; + private readonly ISharedLocations _sharedLocations; + private readonly ICommandLineArguments _commandLineArguments; + + public bool IsRunning { get; private set; } + + public async Task PlayStepsAsync( + IEnumerable composedSteps, + bool dryRun, + CancellationToken cancellationToken = default) + { + var hasAnyFailure = false; + + IsRunning = true; + var catalog = _resourceCacheManager.CatalogDocument; + + foreach (var eachItem in composedSteps) + { + try + { + if (eachItem.InstallItemType == InstallItemType.DownloadAndInstall) + await ProcessDownloadAndInstallAsync(eachItem); + else if (eachItem.InstallItemType == InstallItemType.PowerShellScript) + await ProcessPowerShellScriptAsync(eachItem); + else if (eachItem.InstallItemType == InstallItemType.OpenWebSite) + await OpenAddInWebSiteAsync(eachItem); + else if (eachItem.InstallItemType == InstallItemType.CustomAction) + await eachItem.CustomAction?.Invoke(eachItem); + + eachItem.StatusMessage = UIStringResources.Hostess_Install_Succeed; + eachItem.Installed = true; + eachItem.ErrorMessage = null; + } + catch (Exception ex) + { + hasAnyFailure = true; + eachItem.StatusMessage = UIStringResources.Hostess_Install_Failed; + eachItem.Installed = false; + eachItem.ErrorMessage = ex is AggregateException exception ? exception.InnerException.Message : ex.Message; + await Task.Delay(100); + } + } + + IsRunning = false; + + if (!hasAnyFailure) + { + var parsedArgs = _commandLineArguments.Current; + var targets = parsedArgs.SelectedServices; + + foreach (var eachUrl in catalog.Services.Where(x => targets.Contains(x.Id)).Select(x => x.Url)) + await OpenRequestedWebSite(eachUrl); + } + + return hasAnyFailure; + } + + private async Task ProcessDownloadAndInstallAsync(InstallItemViewModel eachItem) + { + var parsedArgs = _commandLineArguments.Current; + + eachItem.Installed = null; + eachItem.StatusMessage = UIStringResources.Hostess_Download_InProgress; + + var downloadFolderPath = _sharedLocations.GetDownloadDirectoryPath(); + var tempFileName = $"installer_{Guid.NewGuid():n}.exe"; + var tempFilePath = System.IO.Path.Combine(downloadFolderPath, tempFileName); + + if (File.Exists(tempFilePath)) + File.Delete(tempFilePath); + + using (var webClient = new WebClient()) + { + webClient.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml"); + webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko"); + await webClient.DownloadFileTaskAsync(eachItem.PackageUrl, tempFilePath).ConfigureAwait(false); + + eachItem.StatusMessage = UIStringResources.Hostess_Install_InProgress; + + if (parsedArgs.DryRun) + { + await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); + return; + } + + var psi = new ProcessStartInfo(tempFilePath, eachItem.Arguments) + { + UseShellExecute = false, + }; + + var cpSource = new TaskCompletionSource(); + using (var process = new Process() { StartInfo = psi, }) + { + process.EnableRaisingEvents = true; + process.Exited += (_sender, _e) => + { + var realSender = _sender as Process; + cpSource.SetResult(realSender.ExitCode); + }; + + if (!process.Start()) + throw new ApplicationException(ErrorStrings.Error_Package_CanNotStart); + + await cpSource.Task.ConfigureAwait(false); + } + } + } + + private async Task ProcessPowerShellScriptAsync(InstallItemViewModel eachItem) + { + var parsedArgs = _commandLineArguments.Current; + + eachItem.Installed = null; + eachItem.StatusMessage = UIStringResources.Hostess_Install_InProgress; + + var downloadFolderPath = _sharedLocations.GetDownloadDirectoryPath(); + var tempFileName = $"bootstrap_{Guid.NewGuid():n}.ps1"; + var tempFilePath = System.IO.Path.Combine(downloadFolderPath, tempFileName); + + if (File.Exists(tempFilePath)) + File.Delete(tempFilePath); + + File.WriteAllText(tempFilePath, eachItem.ScriptContent, Encoding.Unicode); + var powershellPath = _sharedLocations.GetDefaultPowerShellExecutableFilePath(); + + if (!File.Exists(powershellPath)) + throw new Exception(ErrorStrings.Error_No_WindowsPowerShell); + + if (parsedArgs.DryRun) + { + await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); + return; + } + + var psi = new ProcessStartInfo(powershellPath, $"Set-ExecutionPolicy Bypass -Scope Process -Force; {tempFilePath}") + { + UseShellExecute = false, + }; + + var cpSource = new TaskCompletionSource(); + using (var process = new Process() { StartInfo = psi, }) + { + process.EnableRaisingEvents = true; + process.Exited += (_sender, _e) => + { + var realSender = _sender as Process; + cpSource.SetResult(realSender.ExitCode); + }; + + if (!process.Start()) + throw new ApplicationException(ErrorStrings.Error_Package_CanNotStart); + + await cpSource.Task.ConfigureAwait(false); + } + } + + private async Task OpenAddInWebSiteAsync(InstallItemViewModel viewModel) + { + var parsedArgs = _commandLineArguments.Current; + + if (parsedArgs.DryRun) + { + await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); + return; + } + + Process.Start(new ProcessStartInfo(viewModel.PackageUrl) + { + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Maximized, + }); + } + + private async Task OpenRequestedWebSite(string targetUrl) + { + var parsedArgs = _commandLineArguments.Current; + + if (parsedArgs.DryRun) + { + await Task.Delay(TimeSpan.FromSeconds(1d)).ConfigureAwait(false); + return; + } + + Process.Start(new ProcessStartInfo(targetUrl) + { + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Maximized, + }); + } + } +} diff --git a/src/Hostess/Hostess.csproj b/src/Hostess/Hostess.csproj index a463c9e..0f92fb4 100644 --- a/src/Hostess/Hostess.csproj +++ b/src/Hostess/Hostess.csproj @@ -231,6 +231,7 @@ +