Files
PowerToys/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Pages/InstallPackageCommand.cs
Michael Jolley b552f2ac1e Standardizing built-in extension icon handling (#40606)
Just standardizing built-in extensions to use a `internal sealed class
Icons` for all their non-dynamic icons.

Looks like a LOT of changes, but it's icons all the way down.
2025-07-15 14:33:25 -05:00

253 lines
11 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Management.Deployment;
using Windows.Foundation;
namespace Microsoft.CmdPal.Ext.WinGet.Pages;
public partial class InstallPackageCommand : InvokableCommand
{
private readonly CatalogPackage _package;
private readonly StatusMessage _installBanner = new();
private IAsyncOperationWithProgress<InstallResult, InstallProgress>? _installAction;
private IAsyncOperationWithProgress<UninstallResult, UninstallProgress>? _unInstallAction;
private Task? _installTask;
public PackageInstallCommandState InstallCommandState { get; private set; }
public event EventHandler<InstallPackageCommand>? InstallStateChanged;
private static readonly CompositeFormat UninstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstalling_package);
private static readonly CompositeFormat InstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_installing_package);
private static readonly CompositeFormat InstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finished);
private static readonly CompositeFormat UninstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finished);
private static readonly CompositeFormat QueuedPackageDownload = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_download);
private static readonly CompositeFormat InstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finishing);
private static readonly CompositeFormat QueuedPackageUninstall = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_uninstall);
private static readonly CompositeFormat UninstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finishing);
private static readonly CompositeFormat DownloadProgress = System.Text.CompositeFormat.Parse(Properties.Resources.winget_download_progress);
internal bool SkipDependencies { get; set; }
public InstallPackageCommand(CatalogPackage package, PackageInstallCommandState isInstalled)
{
_package = package;
InstallCommandState = isInstalled;
UpdateAppearance();
}
internal void FakeChangeStatus()
{
InstallCommandState = InstallCommandState switch
{
PackageInstallCommandState.Install => PackageInstallCommandState.Uninstall,
PackageInstallCommandState.Update => PackageInstallCommandState.Uninstall,
PackageInstallCommandState.Uninstall => PackageInstallCommandState.Install,
_ => throw new NotImplementedException(),
};
UpdateAppearance();
}
private void UpdateAppearance()
{
Icon = InstallCommandState switch
{
PackageInstallCommandState.Install => Icons.DownloadIcon,
PackageInstallCommandState.Update => Icons.UpdateIcon,
PackageInstallCommandState.Uninstall => Icons.CompletedIcon,
_ => throw new NotImplementedException(),
};
Name = InstallCommandState switch
{
PackageInstallCommandState.Install => Properties.Resources.winget_install_name,
PackageInstallCommandState.Update => Properties.Resources.winget_update_name,
PackageInstallCommandState.Uninstall => Properties.Resources.winget_uninstall_name,
_ => throw new NotImplementedException(),
};
}
public override ICommandResult Invoke()
{
// TODO: LOCK in here, so this can only be invoked once until the
// install / uninstall is done. Just use like, an atomic
if (_installTask != null)
{
return CommandResult.KeepOpen();
}
if (InstallCommandState == PackageInstallCommandState.Uninstall)
{
// Uninstall
_installBanner.State = MessageState.Info;
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);
var installOptions = WinGetStatics.WinGetFactory.CreateUninstallOptions();
installOptions.PackageUninstallScope = PackageUninstallScope.Any;
_unInstallAction = WinGetStatics.Manager.UninstallPackageAsync(_package, installOptions);
var handler = new AsyncOperationProgressHandler<UninstallResult, UninstallProgress>(OnUninstallProgress);
_unInstallAction.Progress = handler;
_installTask = Task.Run(() => TryDoInstallOperation(_unInstallAction));
}
else if (InstallCommandState is PackageInstallCommandState.Install or
PackageInstallCommandState.Update)
{
// Install
_installBanner.State = MessageState.Info;
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);
var installOptions = WinGetStatics.WinGetFactory.CreateInstallOptions();
installOptions.PackageInstallScope = PackageInstallScope.Any;
installOptions.SkipDependencies = SkipDependencies;
_installAction = WinGetStatics.Manager.InstallPackageAsync(_package, installOptions);
var handler = new AsyncOperationProgressHandler<InstallResult, InstallProgress>(OnInstallProgress);
_installAction.Progress = handler;
_installTask = Task.Run(() => TryDoInstallOperation(_installAction));
}
return CommandResult.KeepOpen();
}
private async void TryDoInstallOperation<T_Operation, T_Progress>(
IAsyncOperationWithProgress<T_Operation, T_Progress> action)
{
try
{
await action.AsTask();
_installBanner.Message = InstallCommandState == PackageInstallCommandState.Uninstall ?
string.Format(CultureInfo.CurrentCulture, UninstallPackageFinished, _package.Name) :
string.Format(CultureInfo.CurrentCulture, InstallPackageFinished, _package.Name);
_installBanner.Progress = null;
_installBanner.State = MessageState.Success;
_installTask = null;
_ = Task.Run(async () =>
{
await Task.Delay(2500).ConfigureAwait(false);
if (_installTask == null)
{
WinGetExtensionHost.Instance.HideStatus(_installBanner);
}
});
InstallStateChanged?.Invoke(this, this);
}
catch (Exception ex)
{
_installBanner.State = MessageState.Error;
_installBanner.Progress = null;
_installBanner.Message = ex.Message;
_installTask = null;
}
}
private static string FormatBytes(ulong bytes)
{
const long KB = 1024;
const long MB = KB * 1024;
const long GB = MB * 1024;
return bytes >= GB
? $"{bytes / (double)GB:F2} GB"
: bytes >= MB ?
$"{bytes / (double)MB:F2} MB"
: bytes >= KB
? $"{bytes / (double)KB:F2} KB"
: $"{bytes} bytes";
}
private void OnInstallProgress(
IAsyncOperationWithProgress<InstallResult, InstallProgress> operation,
InstallProgress progress)
{
switch (progress.State)
{
case PackageInstallProgressState.Queued:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageDownload, _package.Name);
break;
case PackageInstallProgressState.Downloading:
if (progress.BytesRequired > 0)
{
var downloadText = string.Format(CultureInfo.CurrentCulture, DownloadProgress, FormatBytes(progress.BytesDownloaded), FormatBytes(progress.BytesRequired));
_installBanner.Progress ??= new ProgressState() { IsIndeterminate = false };
var downloaded = progress.BytesDownloaded / (float)progress.BytesRequired;
var percent = downloaded * 100.0f;
((ProgressState)_installBanner.Progress).ProgressPercent = (uint)percent;
_installBanner.Message = downloadText;
}
break;
case PackageInstallProgressState.Installing:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
_installBanner.Progress = new ProgressState() { IsIndeterminate = true };
break;
case PackageInstallProgressState.PostInstall:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallPackageFinishing, _package.Name);
break;
case PackageInstallProgressState.Finished:
_installBanner.Message = Properties.Resources.winget_install_finished;
// progressBar.IsIndeterminate(false);
_installBanner.Progress = null;
_installBanner.State = MessageState.Success;
break;
default:
_installBanner.Message = string.Empty;
break;
}
}
private void OnUninstallProgress(
IAsyncOperationWithProgress<UninstallResult, UninstallProgress> operation,
UninstallProgress progress)
{
switch (progress.State)
{
case PackageUninstallProgressState.Queued:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageUninstall, _package.Name);
break;
case PackageUninstallProgressState.Uninstalling:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
_installBanner.Progress = new ProgressState() { IsIndeterminate = true };
break;
case PackageUninstallProgressState.PostUninstall:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallPackageFinishing, _package.Name);
break;
case PackageUninstallProgressState.Finished:
_installBanner.Message = Properties.Resources.winget_uninstall_finished;
_installBanner.Progress = null;
_installBanner.State = MessageState.Success;
break;
default:
_installBanner.Message = string.Empty;
break;
}
}
}
public enum PackageInstallCommandState
{
Uninstall = 0,
Update = 1,
Install = 2,
}