winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 965c05d0730770646fa2348b38375b2a93b50f25
parent 342e15b8bfe8e41d31c9eb88705aa1b6fa2d2677
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Mon, 23 Mar 2020 12:39:50 -0700

Add basic VT support (#62)


Diffstat:
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj | 2++
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters | 6++++++
Msrc/AppInstallerCLICore/Command.cpp | 22+++++++++++-----------
Msrc/AppInstallerCLICore/Command.h | 24++++++++++++------------
Msrc/AppInstallerCLICore/Commands/HashCommand.cpp | 12++++++------
Msrc/AppInstallerCLICore/Commands/HashCommand.h | 2+-
Msrc/AppInstallerCLICore/Commands/InstallCommand.cpp | 36++++++++++++++++++------------------
Msrc/AppInstallerCLICore/Commands/InstallCommand.h | 4++--
Msrc/AppInstallerCLICore/Commands/RootCommand.cpp | 2+-
Msrc/AppInstallerCLICore/Commands/RootCommand.h | 2+-
Msrc/AppInstallerCLICore/Commands/SearchCommand.cpp | 20++++++++++----------
Msrc/AppInstallerCLICore/Commands/SearchCommand.h | 2+-
Msrc/AppInstallerCLICore/Commands/ShowCommand.cpp | 20++++++++++----------
Msrc/AppInstallerCLICore/Commands/ShowCommand.h | 2+-
Msrc/AppInstallerCLICore/Commands/SourceCommand.cpp | 44++++++++++++++++++++++----------------------
Msrc/AppInstallerCLICore/Commands/SourceCommand.h | 10+++++-----
Msrc/AppInstallerCLICore/Core.cpp | 6+++---
Msrc/AppInstallerCLICore/ExecutionArgs.h | 4++--
Msrc/AppInstallerCLICore/ExecutionContext.h | 16+++++++++++-----
Msrc/AppInstallerCLICore/ExecutionReporter.cpp | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/AppInstallerCLICore/ExecutionReporter.h | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Asrc/AppInstallerCLICore/VTSupport.cpp | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerCLICore/VTSupport.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 4++--
Msrc/AppInstallerCLICore/Workflows/InstallFlow.h | 2+-
Msrc/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp | 4++--
Msrc/AppInstallerCLICore/Workflows/InstallerHandlerBase.h | 6+++---
Msrc/AppInstallerCLICore/Workflows/ManifestComparator.cpp | 6+++---
Msrc/AppInstallerCLICore/Workflows/ManifestComparator.h | 8++++----
Msrc/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp | 4++--
Msrc/AppInstallerCLICore/Workflows/MsixInstallerHandler.h | 2+-
Msrc/AppInstallerCLICore/Workflows/SearchFlow.h | 2+-
Msrc/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp | 24++++++++++++------------
Msrc/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h | 2+-
Msrc/AppInstallerCLICore/Workflows/ShowFlow.cpp | 2+-
Msrc/AppInstallerCLICore/Workflows/ShowFlow.h | 2+-
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 48++++++++++++++++++++++++------------------------
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.h | 8++++----
Msrc/AppInstallerCLITests/HashCommand.cpp | 6+++---
Msrc/AppInstallerCLITests/WorkFlow.cpp | 92++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/AppInstallerCommonCore/Public/AppInstallerProgress.h | 29++++++++++++++++++-----------
41 files changed, 634 insertions(+), 323 deletions(-)

diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -187,6 +187,7 @@ <ClInclude Include="pch.h" /> <ClInclude Include="Public\AppInstallerCLICore.h" /> <ClInclude Include="Search\Search.h" /> + <ClInclude Include="VTSupport.h" /> <ClInclude Include="Workflows\Common.h" /> <ClInclude Include="Workflows\SearchFlow.h" /> <ClInclude Include="Workflows\ShellExecuteInstallerHandler.h" /> @@ -210,6 +211,7 @@ <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> + <ClCompile Include="VTSupport.cpp" /> <ClCompile Include="Workflows\SearchFlow.cpp" /> <ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp" /> <ClCompile Include="Workflows\InstallerHandlerBase.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -96,6 +96,9 @@ <ClInclude Include="Commands\HashCommand.h"> <Filter>Commands</Filter> </ClInclude> + <ClInclude Include="VTSupport.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -152,6 +155,9 @@ <ClCompile Include="Commands\HashCommand.cpp"> <Filter>Commands</Filter> </ClCompile> + <ClCompile Include="VTSupport.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp @@ -6,20 +6,20 @@ namespace AppInstaller::CLI { - void Command::OutputIntroHeader(ExecutionReporter& reporter) const + void Command::OutputIntroHeader(Execution::Reporter& reporter) const { reporter.ShowMsg("AppInstaller Command Line"); reporter.ShowMsg("Copyright (c) Microsoft Corporation"); } - void Command::OutputHelp(ExecutionReporter& reporter, const CommandException* exception) const + void Command::OutputHelp(Execution::Reporter& reporter, const CommandException* exception) const { OutputIntroHeader(reporter); reporter.EmptyLine(); if (exception) { - reporter.ShowMsg(exception->Message() + " : '" + std::string(exception->Param()) + '\'', ExecutionReporter::Level::Error); + reporter.ShowMsg(exception->Message() + " : '" + std::string(exception->Param()) + '\'', Execution::Reporter::Level::Error); reporter.EmptyLine(); } @@ -88,7 +88,7 @@ namespace AppInstaller::CLI throw CommandException(LOCME("Unrecognized command"), *itr); } - void Command::ParseArguments(Invocation& inv, ExecutionArgs& execArgs) const + void Command::ParseArguments(Invocation& inv, Execution::Args& execArgs) const { auto definedArgs = GetArguments(); auto positionalSearchItr = definedArgs.begin(); @@ -142,7 +142,7 @@ namespace AppInstaller::CLI if (argName == APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING) { - execArgs.AddArg(ExecutionArgs::Type::Help); + execArgs.AddArg(Execution::Args::Type::Help); } else if (!argFound) { @@ -152,10 +152,10 @@ namespace AppInstaller::CLI } } - void Command::ValidateArguments(ExecutionArgs& execArgs) const + void Command::ValidateArguments(Execution::Args& execArgs) const { // If help is asked for, don't bother validating anything else - if (execArgs.Contains(ExecutionArgs::Type::Help)) + if (execArgs.Contains(Execution::Args::Type::Help)) { return; } @@ -174,10 +174,10 @@ namespace AppInstaller::CLI } } - void Command::Execute(ExecutionContext& context) const + void Command::Execute(Execution::Context& context) const { AICLI_LOG(CLI, Info, << "Executing command: " << Name()); - if (context.Args.Contains(ExecutionArgs::Type::Help)) + if (context.Args.Contains(Execution::Args::Type::Help)) { OutputHelp(context.Reporter); } @@ -187,9 +187,9 @@ namespace AppInstaller::CLI } } - void Command::ExecuteInternal(ExecutionContext& context) const + void Command::ExecuteInternal(Execution::Context& context) const { - context.Reporter.ShowMsg(LOCME("Oops, we forgot to do this..."), ExecutionReporter::Level::Error); + context.Reporter.ShowMsg(LOCME("Oops, we forgot to do this..."), Execution::Reporter::Level::Error); THROW_HR(E_NOTIMPL); } } diff --git a/src/AppInstallerCLICore/Command.h b/src/AppInstallerCLICore/Command.h @@ -43,16 +43,16 @@ namespace AppInstaller::CLI struct Argument { - Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc) : + Argument(std::string_view name, Execution::Args::Type execArgType, std::string desc) : m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)) {} - Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc, bool required) : + Argument(std::string_view name, Execution::Args::Type execArgType, std::string desc, bool required) : m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)), m_required(required) {} - Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc, ArgumentType type) : + Argument(std::string_view name, Execution::Args::Type execArgType, std::string desc, ArgumentType type) : m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type) {} - Argument(std::string_view name, ExecutionArgs::Type execArgType, std::string desc, ArgumentType type, bool required) : + Argument(std::string_view name, Execution::Args::Type execArgType, std::string desc, ArgumentType type, bool required) : m_name(name), m_execArgType(execArgType), m_desc(std::move(desc)), m_type(type), m_required(required) {} ~Argument() = default; @@ -64,7 +64,7 @@ namespace AppInstaller::CLI Argument& operator=(Argument&&) = default; std::string_view Name() const { return m_name; } - ExecutionArgs::Type ExecArgType() const { return m_execArgType; } + Execution::Args::Type ExecArgType() const { return m_execArgType; } std::string Description() const { return m_desc; } bool Required() const { return m_required; } ArgumentType Type() const { return m_type; } @@ -72,7 +72,7 @@ namespace AppInstaller::CLI private: std::string_view m_name; - ExecutionArgs::Type m_execArgType; + Execution::Args::Type m_execArgType; std::string m_desc; bool m_required = false; ArgumentType m_type = ArgumentType::Standard; @@ -98,17 +98,17 @@ namespace AppInstaller::CLI virtual std::string ShortDescription() const { return {}; } virtual std::vector<std::string> GetLongDescription() const { return {}; } - virtual void OutputIntroHeader(ExecutionReporter& reporter) const; - virtual void OutputHelp(ExecutionReporter& reporter, const CommandException* exception = nullptr) const; + virtual void OutputIntroHeader(Execution::Reporter& reporter) const; + virtual void OutputHelp(Execution::Reporter& reporter, const CommandException* exception = nullptr) const; virtual std::unique_ptr<Command> FindInvokedCommand(Invocation& inv) const; - virtual void ParseArguments(Invocation& inv, ExecutionArgs& execArgs) const; - virtual void ValidateArguments(ExecutionArgs& execArgs) const; + virtual void ParseArguments(Invocation& inv, Execution::Args& execArgs) const; + virtual void ValidateArguments(Execution::Args& execArgs) const; - virtual void Execute(ExecutionContext& context) const; + virtual void Execute(Execution::Context& context) const; protected: - virtual void ExecuteInternal(ExecutionContext& context) const; + virtual void ExecuteInternal(Execution::Context& context) const; private: std::string_view m_name; diff --git a/src/AppInstallerCLICore/Commands/HashCommand.cpp b/src/AppInstallerCLICore/Commands/HashCommand.cpp @@ -14,8 +14,8 @@ namespace AppInstaller::CLI std::vector<Argument> HashCommand::GetArguments() const { return { - Argument{ s_HashCommand_ArgName_File, ExecutionArgs::Type::HashFile, LOCME("The input file to be hashed."), ArgumentType::Positional, true }, - Argument{ s_HashCommand_ArgName_Msix, ExecutionArgs::Type::Msix, LOCME("If specified, the input file will be treated as msix. Signature hash will be provided if exists."), ArgumentType::Flag }, + Argument{ s_HashCommand_ArgName_File, Execution::Args::Type::HashFile, LOCME("The input file to be hashed."), ArgumentType::Positional, true }, + Argument{ s_HashCommand_ArgName_Msix, Execution::Args::Type::Msix, LOCME("If specified, the input file will be treated as msix. Signature hash will be provided if exists."), ArgumentType::Flag }, }; } @@ -31,14 +31,14 @@ namespace AppInstaller::CLI }; } - void HashCommand::ExecuteInternal(ExecutionContext& context) const + void HashCommand::ExecuteInternal(Execution::Context& context) const { - auto inputFile = context.Args.GetArg(ExecutionArgs::Type::HashFile); + auto inputFile = context.Args.GetArg(Execution::Args::Type::HashFile); std::ifstream inStream{ *inputFile, std::ifstream::binary }; context.Reporter.ShowMsg("File Hash: " + Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHash(inStream))); - if (context.Args.Contains(ExecutionArgs::Type::Msix)) + if (context.Args.Contains(Execution::Args::Type::Msix)) { try { @@ -52,7 +52,7 @@ namespace AppInstaller::CLI { context.Reporter.ShowMsg( "Failed to calculate signature hash. Please verify the input file is a valid signed msix.", - ExecutionReporter::Level::Warning); + Execution::Reporter::Level::Warning); } } } diff --git a/src/AppInstallerCLICore/Commands/HashCommand.h b/src/AppInstallerCLICore/Commands/HashCommand.h @@ -15,6 +15,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - void ExecuteInternal(ExecutionContext& context) const override; + void ExecuteInternal(Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -31,20 +31,20 @@ namespace AppInstaller::CLI std::vector<Argument> InstallCommand::GetArguments() const { return { - Argument{ s_InstallCommand_ArgName_Query, ExecutionArgs::Type::Query, LOCME("The name of the application to install"), ArgumentType::Positional, false }, - Argument{ s_InstallCommand_ArgName_Manifest, ExecutionArgs::Type::Manifest, LOCME("The path to the manifest of the application to install"), ArgumentType::Standard, false }, - Argument{ s_InstallCommand_ArgName_Id, ExecutionArgs::Type::Id, LOCME("The id of the application to show info"), ArgumentType::Standard }, - Argument{ s_InstallCommand_ArgName_Name, ExecutionArgs::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, - Argument{ s_InstallCommand_ArgName_Moniker, ExecutionArgs::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, - Argument{ s_InstallCommand_ArgName_Version, ExecutionArgs::Type::Version, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, - Argument{ s_InstallCommand_ArgName_Channel, ExecutionArgs::Type::Channel, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, - Argument{ s_InstallCommand_ArgName_Source, ExecutionArgs::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, - Argument{ s_InstallCommand_ArgName_Exact, ExecutionArgs::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, - Argument{ s_InstallCommand_ArgName_Interactive, ExecutionArgs::Type::Interactive, LOCME("The application installation is interactive. User input is needed."), ArgumentType::Flag, false }, - Argument{ s_InstallCommand_ArgName_Silent, ExecutionArgs::Type::Silent, LOCME("The application installation is silent."), ArgumentType::Flag, false }, - Argument{ s_InstallCommand_ArgName_Language, ExecutionArgs::Type::Language, LOCME("Preferred language if application installation supports multiple languages."), ArgumentType::Standard, false }, - Argument{ s_InstallCommand_ArgName_Log, ExecutionArgs::Type::Log, LOCME("Preferred log location if application installation supports custom log path."), ArgumentType::Standard, false }, - Argument{ s_InstallCommand_ArgName_Override, ExecutionArgs::Type::Override, LOCME("Override switches to be passed on to application installer."), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Query, Execution::Args::Type::Query, LOCME("The name of the application to install"), ArgumentType::Positional, false }, + Argument{ s_InstallCommand_ArgName_Manifest, Execution::Args::Type::Manifest, LOCME("The path to the manifest of the application to install"), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Id, Execution::Args::Type::Id, LOCME("The id of the application to show info"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Name, Execution::Args::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Moniker, Execution::Args::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Version, Execution::Args::Type::Version, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Channel, Execution::Args::Type::Channel, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Source, Execution::Args::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ s_InstallCommand_ArgName_Exact, Execution::Args::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + Argument{ s_InstallCommand_ArgName_Interactive, Execution::Args::Type::Interactive, LOCME("The application installation is interactive. User input is needed."), ArgumentType::Flag, false }, + Argument{ s_InstallCommand_ArgName_Silent, Execution::Args::Type::Silent, LOCME("The application installation is silent."), ArgumentType::Flag, false }, + Argument{ s_InstallCommand_ArgName_Language, Execution::Args::Type::Language, LOCME("Preferred language if application installation supports multiple languages."), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Log, Execution::Args::Type::Log, LOCME("Preferred log location if application installation supports custom log path."), ArgumentType::Standard, false }, + Argument{ s_InstallCommand_ArgName_Override, Execution::Args::Type::Override, LOCME("Override switches to be passed on to application installer."), ArgumentType::Standard, false }, }; } @@ -60,23 +60,23 @@ namespace AppInstaller::CLI }; } - void InstallCommand::ExecuteInternal(ExecutionContext& context) const + void InstallCommand::ExecuteInternal(Execution::Context& context) const { InstallFlow appInstall(context); appInstall.Execute(); } - void InstallCommand::ValidateArguments(ExecutionArgs& execArgs) const + void InstallCommand::ValidateArguments(Execution::Args& execArgs) const { Command::ValidateArguments(execArgs); - if (!execArgs.Contains(ExecutionArgs::Type::Query) && !execArgs.Contains(ExecutionArgs::Type::Manifest)) + if (!execArgs.Contains(Execution::Args::Type::Query) && !execArgs.Contains(Execution::Args::Type::Manifest)) { throw CommandException(LOCME("Required argument not provided"), s_InstallCommand_ArgName_Query); } - if (execArgs.Contains(ExecutionArgs::Type::Silent) && execArgs.Contains(ExecutionArgs::Type::Interactive)) + if (execArgs.Contains(Execution::Args::Type::Silent) && execArgs.Contains(Execution::Args::Type::Interactive)) { throw CommandException(LOCME("More than one install behavior argument provided"), s_InstallCommand_ArgName_Query); } diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.h b/src/AppInstallerCLICore/Commands/InstallCommand.h @@ -15,7 +15,7 @@ namespace AppInstaller::CLI std::vector<std::string> GetLongDescription() const override; protected: - void ExecuteInternal(ExecutionContext& context) const override; - void ValidateArguments(ExecutionArgs& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArguments(Execution::Args& execArgs) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -31,7 +31,7 @@ namespace AppInstaller::CLI }; } - void RootCommand::ExecuteInternal(ExecutionContext& context) const + void RootCommand::ExecuteInternal(Execution::Context& context) const { OutputHelp(context.Reporter); } diff --git a/src/AppInstallerCLICore/Commands/RootCommand.h b/src/AppInstallerCLICore/Commands/RootCommand.h @@ -14,6 +14,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(ExecutionContext& context) const; + virtual void ExecuteInternal(Execution::Context& context) const; }; } diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.cpp b/src/AppInstallerCLICore/Commands/SearchCommand.cpp @@ -23,15 +23,15 @@ namespace AppInstaller::CLI std::vector<Argument> SearchCommand::GetArguments() const { return { - Argument{ s_SearchCommand_ArgName_Query, ExecutionArgs::Type::Query, LOCME("The query used to search for an app"), ArgumentType::Positional, false }, - Argument{ s_SearchCommand_ArgName_Id, ExecutionArgs::Type::Id, LOCME("If specified, filter the results by id"), ArgumentType::Standard }, - Argument{ s_SearchCommand_ArgName_Name, ExecutionArgs::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, - Argument{ s_SearchCommand_ArgName_Moniker, ExecutionArgs::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, - Argument{ s_SearchCommand_ArgName_Tag, ExecutionArgs::Type::Tag, LOCME("If specified, filter the results by tag"), ArgumentType::Standard }, - Argument{ s_SearchCommand_ArgName_Command, ExecutionArgs::Type::Command, LOCME("If specified, filter the results by command"), ArgumentType::Standard }, - Argument{ s_SearchCommand_ArgName_Source, ExecutionArgs::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, - Argument{ s_SearchCommand_ArgName_Count, ExecutionArgs::Type::Count, LOCME("If specified, find app and show only up to specified number of results."), ArgumentType::Standard }, - Argument{ s_SearchCommand_ArgName_Exact, ExecutionArgs::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + Argument{ s_SearchCommand_ArgName_Query, Execution::Args::Type::Query, LOCME("The query used to search for an app"), ArgumentType::Positional, false }, + Argument{ s_SearchCommand_ArgName_Id, Execution::Args::Type::Id, LOCME("If specified, filter the results by id"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Name, Execution::Args::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Moniker, Execution::Args::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Tag, Execution::Args::Type::Tag, LOCME("If specified, filter the results by tag"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Command, Execution::Args::Type::Command, LOCME("If specified, filter the results by command"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Source, Execution::Args::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Count, Execution::Args::Type::Count, LOCME("If specified, find app and show only up to specified number of results."), ArgumentType::Standard }, + Argument{ s_SearchCommand_ArgName_Exact, Execution::Args::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, }; } @@ -47,7 +47,7 @@ namespace AppInstaller::CLI }; } - void SearchCommand::ExecuteInternal(ExecutionContext& context) const + void SearchCommand::ExecuteInternal(Execution::Context& context) const { SearchFlow appSearch{ context }; diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.h b/src/AppInstallerCLICore/Commands/SearchCommand.h @@ -15,6 +15,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - void ExecuteInternal(ExecutionContext& context) const override; + void ExecuteInternal(Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -23,15 +23,15 @@ namespace AppInstaller::CLI std::vector<Argument> ShowCommand::GetArguments() const { return { - Argument{ s_ShowCommand_ArgName_Query, ExecutionArgs::Type::Query, LOCME("The query used to search for an app"), ArgumentType::Positional, true }, - Argument{ s_ShowCommand_ArgName_Id, ExecutionArgs::Type::Id, LOCME("The id of the application to show info"), ArgumentType::Standard }, - Argument{ s_ShowCommand_ArgName_Name, ExecutionArgs::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, - Argument{ s_ShowCommand_ArgName_Moniker, ExecutionArgs::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, - Argument{ s_ShowCommand_ArgName_Version, ExecutionArgs::Type::Version, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, - Argument{ s_ShowCommand_ArgName_Channel, ExecutionArgs::Type::Channel, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, - Argument{ s_ShowCommand_ArgName_Source, ExecutionArgs::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, - Argument{ s_ShowCommand_ArgName_Exact, ExecutionArgs::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, - Argument{ s_ShowCommand_ArgName_ListVersions, ExecutionArgs::Type::ListVersions, LOCME("If specified, only show available versions of the app"), ArgumentType::Flag }, + Argument{ s_ShowCommand_ArgName_Query, Execution::Args::Type::Query, LOCME("The query used to search for an app"), ArgumentType::Positional, true }, + Argument{ s_ShowCommand_ArgName_Id, Execution::Args::Type::Id, LOCME("The id of the application to show info"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Name, Execution::Args::Type::Name, LOCME("If specified, filter the results by name"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Moniker, Execution::Args::Type::Moniker, LOCME("If specified, filter the results by app moniker"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Version, Execution::Args::Type::Version, LOCME("If specified, use the specified version. Default is the latest version"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Channel, Execution::Args::Type::Channel, LOCME("If specified, use the specified channel. Default is general audience"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Source, Execution::Args::Type::Source, LOCME("If specified, find app using the specified source. Default is all source"), ArgumentType::Standard }, + Argument{ s_ShowCommand_ArgName_Exact, Execution::Args::Type::Exact, LOCME("If specified, find app using exact match"), ArgumentType::Flag }, + Argument{ s_ShowCommand_ArgName_ListVersions, Execution::Args::Type::ListVersions, LOCME("If specified, only show available versions of the app"), ArgumentType::Flag }, }; } @@ -47,7 +47,7 @@ namespace AppInstaller::CLI }; } - void ShowCommand::ExecuteInternal(ExecutionContext& context) const + void ShowCommand::ExecuteInternal(Execution::Context& context) const { ShowFlow appShowInfo{ context }; diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.h b/src/AppInstallerCLICore/Commands/ShowCommand.h @@ -15,6 +15,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - void ExecuteInternal(AppInstaller::CLI::ExecutionContext& context) const override; + void ExecuteInternal(AppInstaller::CLI::Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -34,7 +34,7 @@ namespace AppInstaller::CLI }; } - void SourceCommand::ExecuteInternal(ExecutionContext& context) const + void SourceCommand::ExecuteInternal(Execution::Context& context) const { OutputHelp(context.Reporter); } @@ -42,9 +42,9 @@ namespace AppInstaller::CLI std::vector<Argument> SourceAddCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source for future reference"), ArgumentType::Positional, true }, - Argument{ s_SourceCommand_ArgName_Arg, ExecutionArgs::Type::SourceArg, LOCME("Argument given to the source"), ArgumentType::Positional, true }, - Argument{ s_SourceCommand_ArgName_Type, ExecutionArgs::Type::SourceType, LOCME("Type of the source"), ArgumentType::Positional, false }, + Argument{ s_SourceCommand_ArgName_Name, Execution::Args::Type::SourceName, LOCME("Name of the source for future reference"), ArgumentType::Positional, true }, + Argument{ s_SourceCommand_ArgName_Arg, Execution::Args::Type::SourceArg, LOCME("Argument given to the source"), ArgumentType::Positional, true }, + Argument{ s_SourceCommand_ArgName_Type, Execution::Args::Type::SourceType, LOCME("Type of the source"), ArgumentType::Positional, false }, }; } @@ -60,14 +60,14 @@ namespace AppInstaller::CLI }; } - void SourceAddCommand::ExecuteInternal(ExecutionContext& context) const + void SourceAddCommand::ExecuteInternal(Execution::Context& context) const { - std::string name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); - std::string arg = *context.Args.GetArg(ExecutionArgs::Type::SourceArg); + std::string name = *context.Args.GetArg(Execution::Args::Type::SourceName); + std::string arg = *context.Args.GetArg(Execution::Args::Type::SourceArg); std::string type; - if (context.Args.Contains(ExecutionArgs::Type::SourceType)) + if (context.Args.Contains(Execution::Args::Type::SourceType)) { - type = *context.Args.GetArg(ExecutionArgs::Type::SourceType); + type = *context.Args.GetArg(Execution::Args::Type::SourceType); } context.Reporter.ShowMsg("Adding source:"); @@ -86,7 +86,7 @@ namespace AppInstaller::CLI std::vector<Argument> SourceListCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source to list full details for"), ArgumentType::Positional, false }, + Argument{ s_SourceCommand_ArgName_Name, Execution::Args::Type::SourceName, LOCME("Name of the source to list full details for"), ArgumentType::Positional, false }, }; } @@ -102,13 +102,13 @@ namespace AppInstaller::CLI }; } - void SourceListCommand::ExecuteInternal(ExecutionContext& context) const + void SourceListCommand::ExecuteInternal(Execution::Context& context) const { std::vector<Repository::SourceDetails> sources = Repository::GetSources(); - if (context.Args.Contains(ExecutionArgs::Type::SourceName)) + if (context.Args.Contains(Execution::Args::Type::SourceName)) { - const std::string& name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); + const std::string& name = *context.Args.GetArg(Execution::Args::Type::SourceName); auto itr = std::find_if(sources.begin(), sources.end(), [name](const Repository::SourceDetails& sd) { return Utility::CaseInsensitiveEquals(sd.Name, name); }); if (itr == sources.end()) @@ -154,7 +154,7 @@ namespace AppInstaller::CLI std::vector<Argument> SourceUpdateCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source to update"), ArgumentType::Positional, false }, + Argument{ s_SourceCommand_ArgName_Name, Execution::Args::Type::SourceName, LOCME("Name of the source to update"), ArgumentType::Positional, false }, }; } @@ -170,16 +170,16 @@ namespace AppInstaller::CLI }; } - void SourceUpdateCommand::ExecuteInternal(ExecutionContext& context) const + void SourceUpdateCommand::ExecuteInternal(Execution::Context& context) const { - if (context.Args.Contains(ExecutionArgs::Type::SourceName)) + if (context.Args.Contains(Execution::Args::Type::SourceName)) { - const std::string& name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); + const std::string& name = *context.Args.GetArg(Execution::Args::Type::SourceName); context.Reporter.ShowMsg("Updating source: " + name + "..."); if (!context.Reporter.ExecuteWithProgress(std::bind(Repository::UpdateSource, name, std::placeholders::_1))) { context.Reporter.EmptyLine(); - context.Reporter.ShowMsg(" Could not find a source by that name.", ExecutionReporter::Level::Warning); + context.Reporter.ShowMsg(" Could not find a source by that name.", Execution::Reporter::Level::Warning); } else { @@ -203,7 +203,7 @@ namespace AppInstaller::CLI std::vector<Argument> SourceRemoveCommand::GetArguments() const { return { - Argument{ s_SourceCommand_ArgName_Name, ExecutionArgs::Type::SourceName, LOCME("Name of the source to remove"), ArgumentType::Positional, true }, + Argument{ s_SourceCommand_ArgName_Name, Execution::Args::Type::SourceName, LOCME("Name of the source to remove"), ArgumentType::Positional, true }, }; } @@ -219,13 +219,13 @@ namespace AppInstaller::CLI }; } - void SourceRemoveCommand::ExecuteInternal(ExecutionContext& context) const + void SourceRemoveCommand::ExecuteInternal(Execution::Context& context) const { - const std::string& name = *context.Args.GetArg(ExecutionArgs::Type::SourceName); + const std::string& name = *context.Args.GetArg(Execution::Args::Type::SourceName); context.Reporter.ShowMsg("Removing source: " + name + "..."); if (!context.Reporter.ExecuteWithProgress(std::bind(Repository::RemoveSource, name, std::placeholders::_1))) { - context.Reporter.ShowMsg("Could not find a source by that name.", ExecutionReporter::Level::Warning); + context.Reporter.ShowMsg("Could not find a source by that name.", Execution::Reporter::Level::Warning); } else { diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.h b/src/AppInstallerCLICore/Commands/SourceCommand.h @@ -15,7 +15,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(ExecutionContext& context) const; + virtual void ExecuteInternal(Execution::Context& context) const; }; struct SourceAddCommand final : public Command @@ -28,7 +28,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(ExecutionContext& context) const override; + virtual void ExecuteInternal(Execution::Context& context) const override; }; struct SourceListCommand final : public Command @@ -41,7 +41,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(ExecutionContext& context) const override; + virtual void ExecuteInternal(Execution::Context& context) const override; }; struct SourceUpdateCommand final : public Command @@ -54,7 +54,7 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(ExecutionContext& context) const override; + virtual void ExecuteInternal(Execution::Context& context) const override; }; struct SourceRemoveCommand final : public Command @@ -67,6 +67,6 @@ namespace AppInstaller::CLI virtual std::vector<std::string> GetLongDescription() const override; protected: - virtual void ExecuteInternal(ExecutionContext& context) const override; + virtual void ExecuteInternal(Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp @@ -53,7 +53,7 @@ namespace AppInstaller::CLI Logging::Telemetry().LogStartup(); - ExecutionContext context{ std::cout, std::cin }; + Execution::Context context{ std::cout, std::cin }; // Convert incoming wide char args to UTF8 std::vector<std::string> utf8Args; @@ -108,14 +108,14 @@ namespace AppInstaller::CLI { // TODO: Better error output std::string message = Utility::ConvertToUTF8(hre.message()); - context.Reporter.ShowMsg("An error occured while executing the command: " + message, ExecutionReporter::Level::Error); + context.Reporter.ShowMsg("An error occured while executing the command: " + message, Execution::Reporter::Level::Error); AICLI_LOG(CLI, Error, << "Error encountered executing command: " << message); return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } catch (const std::exception& e) { // TODO: Better error output - context.Reporter.ShowMsg("An error occured while executing the command: " + std::string(e.what()), ExecutionReporter::Level::Error); + context.Reporter.ShowMsg("An error occured while executing the command: " + std::string(e.what()), Execution::Reporter::Level::Error); AICLI_LOG(CLI, Error, << "Error encountered executing command: " << e.what()); return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h @@ -5,9 +5,9 @@ #include <map> #include <vector> -namespace AppInstaller::CLI +namespace AppInstaller::CLI::Execution { - struct ExecutionArgs + struct Args { enum class Type { diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h @@ -4,13 +4,19 @@ #include "ExecutionReporter.h" #include "ExecutionArgs.h" -namespace AppInstaller::CLI +namespace AppInstaller::CLI::Execution { - struct ExecutionContext + // The context within which all commands execute. + // Contains inout/output via Execution::Reporter and + // arguments via Execution::Args. + struct Context { - ExecutionReporter Reporter; - ExecutionArgs Args; + // The path for console input/output for all functionality. + Reporter Reporter; - ExecutionContext(std::ostream& out, std::istream& in) : Reporter(out, in) {} + // The arguments given to execute with. + Args Args; + + Context(std::ostream& out, std::istream& in) : Reporter(out, in) {} }; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -1,103 +1,174 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #include "pch.h" #include "ExecutionReporter.h" -namespace AppInstaller::CLI + +namespace AppInstaller::CLI::Execution { - void IndefiniteSpinner::ShowSpinner() + namespace details { - if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) + void IndefiniteSpinner::ShowSpinner() { - m_spinnerRunning = true; - m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); + if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) + { + m_spinnerRunning = true; + m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); + } } - } - void IndefiniteSpinner::StopSpinner() - { - if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) + void IndefiniteSpinner::StopSpinner() { - m_canceled = true; - m_spinnerJob.get(); + if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) + { + m_canceled = true; + m_spinnerJob.get(); + } } - } - void IndefiniteSpinner::ShowSpinnerInternal() - { - char spinnerChars[] = { '-', '\\', '|', '/' }; + void IndefiniteSpinner::ShowSpinnerInternal() + { + char spinnerChars[] = { '-', '\\', '|', '/' }; - // First wait for a small amount of time to enable a fast task to skip - // showing anything, or a progress task to skip straight to progress. - Sleep(100); + // First wait for a small amount of time to enable a fast task to skip + // showing anything, or a progress task to skip straight to progress. + Sleep(100); - for (int i = 0; !m_canceled; i++) { - out << '\b' << spinnerChars[i] << std::flush; + for (int i = 0; !m_canceled; i++) { + m_out << '\b' << spinnerChars[i] << std::flush; - if (i == 3) - { - i = -1; + if (i == 3) + { + i = -1; + } + + Sleep(250); } - Sleep(250); + m_out << '\b'; + m_canceled = false; + m_spinnerRunning = false; } - out << '\b'; - m_canceled = false; - m_spinnerRunning = false; - } - - void ProgressBar::ShowProgress(bool running, uint64_t progress) - { - if (running) + void ProgressBar::ShowProgress(bool running, uint64_t progress) { - if (m_isVisible) + if (running) { - out << "\rProgress: " << progress; + if (m_isVisible) + { + m_out << "\rProgress: " << progress; + } + else + { + m_out << "Progress: " << progress; + m_isVisible = true; + } } else { - out << "Progress: " << progress; - m_isVisible = true; + if (m_isVisible) + { + m_out << std::endl; + m_isVisible = false; + } } } - else + } + + Reporter::OutputStream::OutputStream(std::ostream& out, bool enableVT) : + m_out(out), m_isVTEnabled(enableVT) {} + + void Reporter::OutputStream::AddFormat(const VirtualTerminal::Sequence& sequence) + { + m_format.append(sequence.Get()); + } + + void Reporter::OutputStream::ApplyFormat() + { + // Only apply format if m_applyFormatAtOne == 1 coming into this function. + if (m_isVTEnabled && m_applyFormatAtOne) { - if (m_isVisible) + if (!--m_applyFormatAtOne) { - out << std::endl; - m_isVisible = false; + m_out << m_format; } } } - bool ExecutionReporter::PromptForBoolResponse(const std::string& msg, Level level) + Reporter::OutputStream& Reporter::OutputStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) + { + f(m_out); + return *this; + } + + Reporter::OutputStream& Reporter::OutputStream::operator<<(const VirtualTerminal::Sequence& sequence) + { + m_out << sequence; + // An incoming sequence will be valid for 1 "standard" output after this one. + // We set this to 2 to make that happen, because when it is 1, we will output + // the format for the current OutputStream. + m_applyFormatAtOne = 2; + return *this; + } + + Reporter::~Reporter() + { + // The goal of this is to return output to its previous state. + // For now, we assume this means "default". + if (m_consoleMode.IsVTEnabled()) + { + m_out << VirtualTerminal::TextFormat::Default; + } + } + + Reporter::OutputStream Reporter::GetOutputStream(Level level) + { + OutputStream result(m_out, m_consoleMode.IsVTEnabled()); + + switch (level) + { + case Level::Verbose: + result.AddFormat(VirtualTerminal::TextFormat::Default); + break; + case Level::Info: + result.AddFormat(VirtualTerminal::TextFormat::Default); + break; + case Level::Warning: + result.AddFormat(VirtualTerminal::TextFormat::Foreground::BrightYellow); + break; + case Level::Error: + result.AddFormat(VirtualTerminal::TextFormat::Foreground::BrightRed); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + bool Reporter::PromptForBoolResponse(const std::string& msg, Level level) { UNREFERENCED_PARAMETER(level); - out << msg << " (Y|N)" << std::endl; + m_out << msg << " (Y|N)" << std::endl; char response; - in.get(response); + m_in.get(response); return tolower(response) == 'y'; } - void ExecutionReporter::ShowMsg(const std::string& msg, Level level) + void Reporter::ShowMsg(const std::string& msg, Level level) { - UNREFERENCED_PARAMETER(level); - - // Todo: color output using level and possibly other factors. - out << msg << std::endl; + GetOutputStream(level) << msg << std::endl; } - void ExecutionReporter::ShowProgress(bool running, uint64_t progress) + void Reporter::ShowProgress(bool running, uint64_t progress) { m_progressBar.ShowProgress(running, progress); } - void ExecutionReporter::ShowIndefiniteProgress(bool running) + void Reporter::ShowIndefiniteProgress(bool running) { if (running) { @@ -109,7 +180,7 @@ namespace AppInstaller::CLI } } - void ExecutionReporter::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) + void Reporter::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) { UNREFERENCED_PARAMETER(type); ShowIndefiniteProgress(false); diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "AppInstallerProgress.h" +#include "VTSupport.h" +#include <AppInstallerProgress.h> #include <wil/resource.h> @@ -10,43 +11,48 @@ #include <istream> #include <ostream> #include <string> +#include <vector> -namespace AppInstaller::CLI + +namespace AppInstaller::CLI::Execution { - // Class to print a indefinite spinner. - class IndefiniteSpinner + namespace details { - public: - IndefiniteSpinner(std::ostream& stream) : out(stream) {}; + // Class to print a indefinite spinner. + class IndefiniteSpinner + { + public: + IndefiniteSpinner(std::ostream& stream) : m_out(stream) {} - void ShowSpinner(); - void StopSpinner(); + void ShowSpinner(); + void StopSpinner(); - private: - std::atomic<bool> m_canceled = false; - std::atomic<bool> m_spinnerRunning = false; - std::future<void> m_spinnerJob; - std::ostream& out; + private: + std::atomic<bool> m_canceled = false; + std::atomic<bool> m_spinnerRunning = false; + std::future<void> m_spinnerJob; + std::ostream& m_out; - void ShowSpinnerInternal(); - }; + void ShowSpinnerInternal(); + }; - // Todo: Need to implement real progress bar. Only prints progress number now. - class ProgressBar - { - public: - ProgressBar(std::ostream& stream) : out(stream) {}; + // Todo: Need to implement real progress bar. Only prints progress number now. + class ProgressBar + { + public: + ProgressBar(std::ostream& stream) : m_out(stream) {} - void ShowProgress(bool running, uint64_t progress); + void ShowProgress(bool running, uint64_t progress); - private: - std::atomic<bool> m_isVisible = false; - std::ostream& out; - }; + private: + std::atomic<bool> m_isVisible = false; + std::ostream& m_out; + }; + } // WorkflowReporter should be the central place to show workflow status to user. // Todo: need to implement actual console output to show color, progress bar, etc - struct ExecutionReporter : public IProgressCallback + struct Reporter : public IProgressSink { enum class Level { @@ -56,10 +62,56 @@ namespace AppInstaller::CLI Error, }; - ExecutionReporter(std::ostream& outStream, std::istream& inStream) : - out(outStream), in(inStream), m_progressBar(outStream), m_spinner(outStream) {}; + Reporter(std::ostream& outStream, std::istream& inStream) : + m_out(outStream), m_in(inStream), m_progressBar(outStream), m_spinner(outStream) {} + + ~Reporter(); + + // Holds output formatting information. + struct OutputStream + { + OutputStream(std::ostream& out, bool enableVT); + + // Adds a format to the current value. + void AddFormat(const VirtualTerminal::Sequence& sequence); + + template <typename T> + OutputStream& operator<<(T&& t) + { + ApplyFormat(); + m_out << std::forward<T>(t); + return *this; + } + + OutputStream& operator<<(std::ostream& (__cdecl* f)(std::ostream&)); + OutputStream& operator<<(const VirtualTerminal::Sequence& sequence); + + private: + // Applies the format for the stream. + void ApplyFormat(); + + std::ostream& m_out; + bool m_isVTEnabled; + size_t m_applyFormatAtOne = 1; + std::string m_format; + }; + + // Get a stream for verbose output. + OutputStream Verbose() { return GetOutputStream(Level::Verbose); } + + // Get a stream for informational output. + OutputStream Info() { return GetOutputStream(Level::Info); } + + // Get a stream for warning output. + OutputStream Warn() { return GetOutputStream(Level::Warning); } + + // Get a stream for error output. + OutputStream Error() { return GetOutputStream(Level::Error); } + + // Gets a stream for output of the given level. + OutputStream GetOutputStream(Level level); - void EmptyLine() { out << std::endl; } + void EmptyLine() { m_out << std::endl; } bool PromptForBoolResponse(const std::string& msg, Level level = Level::Info); @@ -74,10 +126,8 @@ namespace AppInstaller::CLI // running: shows indefinite progress if set to true, stops indefinite progress if set to false void ShowIndefiniteProgress(bool running); - // IProgressCallback + // IProgressSink void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; - bool IsCancelled() override { return false; } - [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function<void()>&&) override { return {}; } // Runs the given callable of type: auto(IProgressCallback&) template <typename F> @@ -95,9 +145,10 @@ namespace AppInstaller::CLI } private: - std::ostream& out; - std::istream& in; - IndefiniteSpinner m_spinner; - ProgressBar m_progressBar; + std::ostream& m_out; + std::istream& m_in; + VirtualTerminal::ConsoleModeRestore m_consoleMode; + details::IndefiniteSpinner m_spinner; + details::ProgressBar m_progressBar; }; -}- \ No newline at end of file +} diff --git a/src/AppInstallerCLICore/VTSupport.cpp b/src/AppInstallerCLICore/VTSupport.cpp @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "VTSupport.h" + + +namespace AppInstaller::CLI::VirtualTerminal +{ + ConsoleModeRestore::ConsoleModeRestore(bool enableVTProcessing) + { + if (enableVTProcessing) + { + // Set output mode to handle virtual terminal sequences + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) + { + LOG_LAST_ERROR(); + } + else if (hOut == NULL) + { + AICLI_LOG(CLI, Info, << "VT not enabled due to null output handle"); + } + else + { + if (!GetConsoleMode(hOut, &m_previousMode)) + { + LOG_LAST_ERROR(); + } + else + { + // Try to degrade in case DISABLE_NEWLINE_AUTO_RETURN isn't supported. + for (DWORD mode : { ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN, ENABLE_VIRTUAL_TERMINAL_PROCESSING}) + { + DWORD outMode = m_previousMode | mode; + if (!SetConsoleMode(hOut, outMode)) + { + // Even if it is a different error, log it and try to carry on. + LOG_LAST_ERROR_IF(GetLastError() != STATUS_INVALID_PARAMETER); + } + else + { + m_token = true; + m_isVTEnabled = true; + break; + } + } + } + } + } + } + + ConsoleModeRestore::~ConsoleModeRestore() + { + if (m_token) + { + LOG_LAST_ERROR_IF(!SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), m_previousMode)); + m_token = false; + } + } + +// The escape character that begins all VT sequences +#define AICLI_VT_ESCAPE "\x1b" + +// The beginning of a Control Sequence Introducer +#define AICLI_VT_CSI AICLI_VT_ESCAPE "[" + +// The beginning of an Operating system command +#define AICLI_VT_OSC AICLI_VT_ESCAPE "]" + + namespace TextFormat + { +// Define a text formatting sequence with an integer id +#define AICLI_VT_TEXTFORMAT(_id_) AICLI_VT_CSI #_id_ "m" + + Sequence Default = AICLI_VT_TEXTFORMAT(0); + + namespace Foreground + { + Sequence BrightRed = AICLI_VT_TEXTFORMAT(91); + Sequence BrightYellow = AICLI_VT_TEXTFORMAT(93); + } + + namespace Background + { + + } + }; +} diff --git a/src/AppInstallerCLICore/VTSupport.h b/src/AppInstallerCLICore/VTSupport.h @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <AppInstallerLanguageUtilities.h> + +#include <iostream> +#include <string> + + +namespace AppInstaller::CLI::VirtualTerminal +{ + // RAII class to enable VT support and restore the console mode. + struct ConsoleModeRestore + { + ConsoleModeRestore(bool enableVTProcessing = true); + + ~ConsoleModeRestore(); + + ConsoleModeRestore(const ConsoleModeRestore&) = delete; + ConsoleModeRestore& operator=(const ConsoleModeRestore&) = delete; + + ConsoleModeRestore(ConsoleModeRestore&&) = default; + ConsoleModeRestore& operator=(ConsoleModeRestore&&) = default; + + bool IsVTEnabled() const { return m_isVTEnabled; } + + private: + bool m_isVTEnabled = false; + DestructionToken m_token = false; + DWORD m_previousMode = 0; + }; + + // The base for all VT sequences. + struct Sequence + { + Sequence() = default; + Sequence(const char* c) : m_chars(c) {} + + const char* Get() const { return m_chars; } + + protected: + void Set(const std::string& s) { m_chars = s.c_str(); } + + private: + const char* m_chars = nullptr; + }; + + // A VT sequence that is constructed at runtime. + struct ConstructedSequence : public Sequence + { + ConstructedSequence(std::string s) : m_str(std::move(s)) { Set(m_str); } + + private: + std::string m_str; + }; + + // Below are mapped to the sequences described here: + // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences + + namespace TextFormat + { + // Returns all attributes to the default state prior to modification + extern Sequence Default; + + namespace Foreground + { + extern Sequence BrightRed; + extern Sequence BrightYellow; + } + + namespace Background + { + + } + } +} + +inline std::ostream& operator<<(std::ostream& o, const AppInstaller::CLI::VirtualTerminal::Sequence& s) +{ + return (o << s.Get()); +} diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -14,9 +14,9 @@ namespace AppInstaller::Workflow { void InstallFlow::Execute() { - if (m_argsRef.Contains(ExecutionArgs::Type::Manifest)) + if (m_argsRef.Contains(Execution::Args::Type::Manifest)) { - m_manifest = Manifest::Manifest::CreateFromPath(*(m_argsRef.GetArg(ExecutionArgs::Type::Manifest))); + m_manifest = Manifest::Manifest::CreateFromPath(*(m_argsRef.GetArg(Execution::Args::Type::Manifest))); } else { diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -12,7 +12,7 @@ namespace AppInstaller::Workflow class InstallFlow : public SingleManifestWorkflow { public: - InstallFlow(AppInstaller::CLI::ExecutionContext& context) : SingleManifestWorkflow(context) {} + InstallFlow(AppInstaller::CLI::Execution::Context& context) : SingleManifestWorkflow(context) {} // Execute will perform a query against index and do app install if a target app is found. // If a manifest is given with /manifest, use the manifest and no index search is performed. diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp @@ -40,9 +40,9 @@ namespace AppInstaller::Workflow << " SHA256 from download: " << Utility::SHA256::ConvertToString(hash.value())); - if (!m_reporterRef.PromptForBoolResponse("Package hash verification failed. Continue?", ExecutionReporter::Level::Warning)) + if (!m_reporterRef.PromptForBoolResponse("Package hash verification failed. Continue?", Execution::Reporter::Level::Warning)) { - m_reporterRef.ShowMsg("Canceled. Package hash mismatch.", ExecutionReporter::Level::Error); + m_reporterRef.ShowMsg("Canceled. Package hash mismatch.", Execution::Reporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Package installation canceled"); } } diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h @@ -31,12 +31,12 @@ namespace AppInstaller::Workflow protected: InstallerHandlerBase( const Manifest::ManifestInstaller& manifestInstaller, - AppInstaller::CLI::ExecutionContext& context) : + AppInstaller::CLI::Execution::Context& context) : m_manifestInstallerRef(manifestInstaller), m_reporterRef(context.Reporter), m_argsRef(context.Args) {}; const Manifest::ManifestInstaller& m_manifestInstallerRef; - const AppInstaller::CLI::ExecutionArgs& m_argsRef; - AppInstaller::CLI::ExecutionReporter& m_reporterRef; + const AppInstaller::CLI::Execution::Args& m_argsRef; + AppInstaller::CLI::Execution::Reporter& m_reporterRef; std::filesystem::path m_downloadedInstaller; }; } diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -38,7 +38,7 @@ namespace AppInstaller::Workflow return true; } - ManifestInstaller ManifestComparator::GetPreferredInstaller(const ExecutionArgs&) + ManifestInstaller ManifestComparator::GetPreferredInstaller(const Execution::Args&) { AICLI_LOG(CLI, Info, << "Starting installer selection."); @@ -48,7 +48,7 @@ namespace AppInstaller::Workflow // If the first one is inapplicable, then no installer is applicable. if (Utility::IsApplicableArchitecture(m_manifestRef.Installers[0].Arch) == -1) { - m_reporterRef.ShowMsg("No applicable installer found.", ExecutionReporter::Level::Error); + m_reporterRef.ShowMsg("No applicable installer found.", Execution::Reporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_WORKFLOW_FAILED), "No installer with applicable architecture found."); } @@ -59,7 +59,7 @@ namespace AppInstaller::Workflow return selectedInstaller; } - ManifestLocalization ManifestComparator::GetPreferredLocalization(const ExecutionArgs&) + ManifestLocalization ManifestComparator::GetPreferredLocalization(const Execution::Args&) { AICLI_LOG(CLI, Info, << "Starting localization selection."); diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -26,14 +26,14 @@ namespace AppInstaller::Workflow class ManifestComparator { public: - ManifestComparator(AppInstaller::Manifest::Manifest& manifest, AppInstaller::CLI::ExecutionReporter& reporter) : m_manifestRef(manifest), m_reporterRef(reporter) {} + ManifestComparator(AppInstaller::Manifest::Manifest& manifest, AppInstaller::CLI::Execution::Reporter& reporter) : m_manifestRef(manifest), m_reporterRef(reporter) {} - AppInstaller::Manifest::ManifestInstaller GetPreferredInstaller(const AppInstaller::CLI::ExecutionArgs& args); - AppInstaller::Manifest::ManifestLocalization GetPreferredLocalization(const AppInstaller::CLI::ExecutionArgs& args); + AppInstaller::Manifest::ManifestInstaller GetPreferredInstaller(const AppInstaller::CLI::Execution::Args& args); + AppInstaller::Manifest::ManifestLocalization GetPreferredLocalization(const AppInstaller::CLI::Execution::Args& args); private: AppInstaller::Manifest::Manifest& m_manifestRef; - AppInstaller::CLI::ExecutionReporter& m_reporterRef; + AppInstaller::CLI::Execution::Reporter& m_reporterRef; }; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp @@ -40,9 +40,9 @@ namespace AppInstaller::Workflow << "Signature SHA256 from download: " << SHA256::ConvertToString(signatureHash)); - if (!m_reporterRef.PromptForBoolResponse("Package hash verification failed. Continue?", ExecutionReporter::Level::Warning)) + if (!m_reporterRef.PromptForBoolResponse("Package hash verification failed. Continue?", Execution::Reporter::Level::Warning)) { - m_reporterRef.ShowMsg("Canceled. Package hash mismatch.", ExecutionReporter::Level::Error); + m_reporterRef.ShowMsg("Canceled. Package hash mismatch.", Execution::Reporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Package installation canceled"); } } diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h @@ -11,7 +11,7 @@ namespace AppInstaller::Workflow public: MsixInstallerHandler( const Manifest::ManifestInstaller& manifestInstaller, - AppInstaller::CLI::ExecutionContext& context) : + AppInstaller::CLI::Execution::Context& context) : InstallerHandlerBase(manifestInstaller, context) {} // Download method just checks installer signature hash if signature hash diff --git a/src/AppInstallerCLICore/Workflows/SearchFlow.h b/src/AppInstallerCLICore/Workflows/SearchFlow.h @@ -10,7 +10,7 @@ namespace AppInstaller::Workflow class SearchFlow : public WorkflowBase { public: - SearchFlow(AppInstaller::CLI::ExecutionContext& context) : WorkflowBase(context) {} + SearchFlow(AppInstaller::CLI::Execution::Context& context) : WorkflowBase(context) {} void Execute();; diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -32,11 +32,11 @@ namespace AppInstaller::Workflow if (!installResult) { - m_reporterRef.ShowMsg("Installation abandoned", ExecutionReporter::Level::Error); + m_reporterRef.ShowMsg("Installation abandoned", Execution::Reporter::Level::Error); } else if (installResult.value() != 0) { - m_reporterRef.ShowMsg("Install failed. Exit code: " + std::to_string(installResult.value()), ExecutionReporter::Level::Error); + m_reporterRef.ShowMsg("Install failed. Exit code: " + std::to_string(installResult.value()), Execution::Reporter::Level::Error); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Install failed. Installer task returned: %u", installResult.value()); @@ -99,11 +99,11 @@ namespace AppInstaller::Workflow const std::map<ManifestInstaller::InstallerSwitchType, Utility::NormalizedString>& installerSwitches = m_manifestInstallerRef.Switches; // Construct install experience arg. - if (m_argsRef.Contains(ExecutionArgs::Type::Silent) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Silent) != installerSwitches.end()) + if (m_argsRef.Contains(Execution::Args::Type::Silent) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Silent) != installerSwitches.end()) { installerArgs += installerSwitches.at(ManifestInstaller::InstallerSwitchType::Silent); } - else if (m_argsRef.Contains(ExecutionArgs::Type::Interactive) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Interactive) != installerSwitches.end()) + else if (m_argsRef.Contains(Execution::Args::Type::Interactive) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Interactive) != installerSwitches.end()) { installerArgs += installerSwitches.at(ManifestInstaller::InstallerSwitchType::Interactive); } @@ -113,13 +113,13 @@ namespace AppInstaller::Workflow } // Construct language arg if necessary. - if (m_argsRef.Contains(ExecutionArgs::Type::Language) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Language) != installerSwitches.end()) + if (m_argsRef.Contains(Execution::Args::Type::Language) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::Language) != installerSwitches.end()) { installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::Language); } // Construct install location arg if necessary. - if (m_argsRef.Contains(ExecutionArgs::Type::InstallLocation) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::InstallLocation) != installerSwitches.end()) + if (m_argsRef.Contains(Execution::Args::Type::InstallLocation) && installerSwitches.find(ManifestInstaller::InstallerSwitchType::InstallLocation) != installerSwitches.end()) { installerArgs += ' ' + installerSwitches.at(ManifestInstaller::InstallerSwitchType::InstallLocation); } @@ -143,9 +143,9 @@ namespace AppInstaller::Workflow { // Populate <LogPath> with value from command line or temp path. std::string logPath; - if (m_argsRef.Contains(ExecutionArgs::Type::Log)) + if (m_argsRef.Contains(Execution::Args::Type::Log)) { - logPath = *m_argsRef.GetArg(ExecutionArgs::Type::Log); + logPath = *m_argsRef.GetArg(Execution::Args::Type::Log); } else { @@ -154,9 +154,9 @@ namespace AppInstaller::Workflow Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_LOGPATH), logPath); // Populate <InstallPath> with value from command line. - if (m_argsRef.Contains(ExecutionArgs::Type::InstallLocation)) + if (m_argsRef.Contains(Execution::Args::Type::InstallLocation)) { - Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), *m_argsRef.GetArg(ExecutionArgs::Type::InstallLocation)); + Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), *m_argsRef.GetArg(Execution::Args::Type::InstallLocation)); } // Todo: language token support will be implemented later @@ -165,9 +165,9 @@ namespace AppInstaller::Workflow std::string ShellExecuteInstallerHandler::GetInstallerArgs() { // If override switch is specified, use the override value as installer args. - if (m_argsRef.Contains(ExecutionArgs::Type::Override)) + if (m_argsRef.Contains(Execution::Args::Type::Override)) { - return *m_argsRef.GetArg(ExecutionArgs::Type::Override); + return *m_argsRef.GetArg(Execution::Args::Type::Override); } std::string installerArgs = GetInstallerArgsTemplate(); diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h @@ -15,7 +15,7 @@ namespace AppInstaller::Workflow public: ShellExecuteInstallerHandler( const Manifest::ManifestInstaller& manifestInstaller, - AppInstaller::CLI::ExecutionContext& context) : + AppInstaller::CLI::Execution::Context& context) : InstallerHandlerBase(manifestInstaller, context) {}; // Install is done though invoking SheelExecute on downloaded installer. diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -14,7 +14,7 @@ namespace AppInstaller::Workflow { if (IndexSearch() && EnsureOneMatchFromSearchResult()) { - if (m_argsRef.Contains(ExecutionArgs::Type::ListVersions)) + if (m_argsRef.Contains(Execution::Args::Type::ListVersions)) { ShowAppVersion(); } diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.h b/src/AppInstallerCLICore/Workflows/ShowFlow.h @@ -10,7 +10,7 @@ namespace AppInstaller::Workflow class ShowFlow : public SingleManifestWorkflow { public: - ShowFlow(AppInstaller::CLI::ExecutionContext& context) : SingleManifestWorkflow(context) {} + ShowFlow(AppInstaller::CLI::Execution::Context& context) : SingleManifestWorkflow(context) {} void Execute(); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -12,9 +12,9 @@ namespace AppInstaller::Workflow void WorkflowBase::OpenIndexSource() { std::string sourceName; - if (m_argsRef.Contains(ExecutionArgs::Type::Source)) + if (m_argsRef.Contains(Execution::Args::Type::Source)) { - sourceName = *m_argsRef.GetArg(ExecutionArgs::Type::Source); + sourceName = *m_argsRef.GetArg(Execution::Args::Type::Source); } m_source = m_reporterRef.ExecuteWithProgress(std::bind(OpenSource, sourceName, std::placeholders::_1)); @@ -27,7 +27,7 @@ namespace AppInstaller::Workflow { bool noSources = true; - if (m_argsRef.Contains(ExecutionArgs::Type::Source)) + if (m_argsRef.Contains(Execution::Args::Type::Source)) { // A bad name was given, try to help. std::vector<SourceDetails> sources = GetSources(); @@ -35,8 +35,8 @@ namespace AppInstaller::Workflow { noSources = false; - m_reporterRef.ShowMsg("No sources match the given value '" + *m_argsRef.GetArg(ExecutionArgs::Type::Source) + "'", - ExecutionReporter::Level::Warning); + m_reporterRef.ShowMsg("No sources match the given value '" + *m_argsRef.GetArg(Execution::Args::Type::Source) + "'", + Execution::Reporter::Level::Warning); m_reporterRef.ShowMsg("The configured sources are:"); for (const auto& details : sources) { @@ -48,7 +48,7 @@ namespace AppInstaller::Workflow if (noSources) { m_reporterRef.ShowMsg("No sources defined; add one with 'source add'", - ExecutionReporter::Level::Warning); + Execution::Reporter::Level::Warning); } return false; @@ -56,45 +56,45 @@ namespace AppInstaller::Workflow // Construct query MatchType matchType = MatchType::Substring; - if (m_argsRef.Contains(ExecutionArgs::Type::Exact)) + if (m_argsRef.Contains(Execution::Args::Type::Exact)) { matchType = MatchType::Exact; } SearchRequest searchRequest; - if (m_argsRef.Contains(ExecutionArgs::Type::Query)) + if (m_argsRef.Contains(Execution::Args::Type::Query)) { - searchRequest.Query.emplace(RequestMatch(matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Query))); + searchRequest.Query.emplace(RequestMatch(matchType, *m_argsRef.GetArg(Execution::Args::Type::Query))); } - if (m_argsRef.Contains(ExecutionArgs::Type::Id)) + if (m_argsRef.Contains(Execution::Args::Type::Id)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Id, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Id))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Id, matchType, *m_argsRef.GetArg(Execution::Args::Type::Id))); } - if (m_argsRef.Contains(ExecutionArgs::Type::Name)) + if (m_argsRef.Contains(Execution::Args::Type::Name)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Name, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Name))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Name, matchType, *m_argsRef.GetArg(Execution::Args::Type::Name))); } - if (m_argsRef.Contains(ExecutionArgs::Type::Moniker)) + if (m_argsRef.Contains(Execution::Args::Type::Moniker)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Moniker, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Moniker))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Moniker, matchType, *m_argsRef.GetArg(Execution::Args::Type::Moniker))); } - if (m_argsRef.Contains(ExecutionArgs::Type::Tag)) + if (m_argsRef.Contains(Execution::Args::Type::Tag)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Tag, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Tag))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Tag, matchType, *m_argsRef.GetArg(Execution::Args::Type::Tag))); } - if (m_argsRef.Contains(ExecutionArgs::Type::Command)) + if (m_argsRef.Contains(Execution::Args::Type::Command)) { - searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Command, matchType, *m_argsRef.GetArg(ExecutionArgs::Type::Command))); + searchRequest.Filters.emplace_back(ApplicationMatchFilter(ApplicationMatchField::Command, matchType, *m_argsRef.GetArg(Execution::Args::Type::Command))); } - if (m_argsRef.Contains(ExecutionArgs::Type::Count)) + if (m_argsRef.Contains(Execution::Args::Type::Count)) { - searchRequest.MaximumResults = std::stoi(*m_argsRef.GetArg(ExecutionArgs::Type::Count)); + searchRequest.MaximumResults = std::stoi(*m_argsRef.GetArg(Execution::Args::Type::Count)); } m_searchResult = m_source->Search(searchRequest); @@ -151,8 +151,8 @@ namespace AppInstaller::Workflow Logging::Telemetry().LogManifestFields(app->GetName(), app->GetId()); - std::string_view version = (m_argsRef.Contains(ExecutionArgs::Type::Version) ? *m_argsRef.GetArg(ExecutionArgs::Type::Version) : std::string_view{}); - std::string_view channel = (m_argsRef.Contains(ExecutionArgs::Type::Channel) ? *m_argsRef.GetArg(ExecutionArgs::Type::Channel) : std::string_view{}); + std::string_view version = (m_argsRef.Contains(Execution::Args::Type::Version) ? *m_argsRef.GetArg(Execution::Args::Type::Version) : std::string_view{}); + std::string_view channel = (m_argsRef.Contains(Execution::Args::Type::Channel) ? *m_argsRef.GetArg(Execution::Args::Type::Channel) : std::string_view{}); std::optional<Manifest::Manifest> manifest = app->GetManifest(version, channel); @@ -170,7 +170,7 @@ namespace AppInstaller::Workflow message += ']'; } - m_reporterRef.ShowMsg(message, ExecutionReporter::Level::Warning); + m_reporterRef.ShowMsg(message, Execution::Reporter::Level::Warning); return false; } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -11,12 +11,12 @@ namespace AppInstaller::Workflow class WorkflowBase { protected: - WorkflowBase(AppInstaller::CLI::ExecutionContext& context) : + WorkflowBase(AppInstaller::CLI::Execution::Context& context) : m_contextRef(context), m_reporterRef(context.Reporter), m_argsRef(context.Args) {} - AppInstaller::CLI::ExecutionContext& m_contextRef; - AppInstaller::CLI::ExecutionReporter& m_reporterRef; - const AppInstaller::CLI::ExecutionArgs& m_argsRef; + AppInstaller::CLI::Execution::Context& m_contextRef; + AppInstaller::CLI::Execution::Reporter& m_reporterRef; + const AppInstaller::CLI::Execution::Args& m_argsRef; virtual void OpenIndexSource(); diff --git a/src/AppInstallerCLITests/HashCommand.cpp b/src/AppInstallerCLITests/HashCommand.cpp @@ -11,9 +11,9 @@ using namespace AppInstaller::CLI; TEST_CASE("HashCommandWithTestMsix", "[Sha256Hash]") { std::ostringstream hashOutput; - ExecutionContext context{ hashOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::HashFile, TestDataFile("TestSignedApp.msix").GetPath().u8string()); - context.Args.AddArg(ExecutionArgs::Type::Msix); + Execution::Context context{ hashOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::HashFile, TestDataFile("TestSignedApp.msix").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Msix); HashCommand hashCommand; hashCommand.Execute(context); diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -27,7 +27,7 @@ class MsixInstallerHandlerTest : public MsixInstallerHandler public: MsixInstallerHandlerTest( const ManifestInstaller& manifestInstaller, - ExecutionContext& context) : MsixInstallerHandler(manifestInstaller, context) {}; + Execution::Context& context) : MsixInstallerHandler(manifestInstaller, context) {}; protected: @@ -48,7 +48,7 @@ class ShellExecuteInstallerHandlerTest : public ShellExecuteInstallerHandler public: ShellExecuteInstallerHandlerTest( const ManifestInstaller& manifestInstaller, - ExecutionContext& context) : ShellExecuteInstallerHandler(manifestInstaller, context) {}; + Execution::Context& context) : ShellExecuteInstallerHandler(manifestInstaller, context) {}; void Download() override { @@ -132,7 +132,7 @@ struct TestSource : public ISource class InstallFlowTest : public InstallFlow { public: - InstallFlowTest(ExecutionContext& context) : InstallFlow(context) {} + InstallFlowTest(Execution::Context& context) : InstallFlow(context) {} protected: std::unique_ptr<InstallerHandlerBase> GetInstallerHandler() override @@ -157,7 +157,7 @@ protected: class ShowFlowTest : public ShowFlow { public: - ShowFlowTest(ExecutionContext& context) : ShowFlow(context) {} + ShowFlowTest(Execution::Context& context) : ShowFlow(context) {} protected: @@ -172,8 +172,8 @@ TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow]") TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yml").GetPath().u8string()); + Execution::Context context{ installOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yml").GetPath().u8string()); InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -193,8 +193,8 @@ TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow]") TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yml").GetPath().u8string()); + Execution::Context context{ installOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yml").GetPath().u8string()); InstallFlowTest testFlow(context); REQUIRE_THROWS_WITH(testFlow.Execute(), Catch::Contains("No installer with applicable architecture found.")); INFO(installOutput.str()); @@ -208,9 +208,9 @@ TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow]") TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Todo: point to files from our repo when the repo goes public - context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yml").GetPath().u8string()); InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -229,9 +229,9 @@ TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow]") TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Todo: point to files from our repo when the repo goes public - context.Args.AddArg(ExecutionArgs::Type::Manifest, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yml").GetPath().u8string()); InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -249,7 +249,7 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Default Msi type with no args passed in, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); @@ -260,12 +260,12 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yml")); - context.Args.AddArg(ExecutionArgs::Type::Silent); - context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); - context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"); ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/quiet") != std::string::npos); @@ -275,12 +275,12 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yml")); - context.Args.AddArg(ExecutionArgs::Type::Silent); - context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); - context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"); ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest @@ -291,7 +291,7 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Default Inno type with no args passed in, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); @@ -302,12 +302,12 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yml")); - context.Args.AddArg(ExecutionArgs::Type::Silent); - context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); - context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"); ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); @@ -317,12 +317,12 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); - context.Args.AddArg(ExecutionArgs::Type::Silent); - context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); - context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"); ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest @@ -333,13 +333,13 @@ TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; + Execution::Context context{ installOutput, std::cin }; // Override switch specified. The whole arg passed to installer is overrided. auto manifest = Manifest::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yml")); - context.Args.AddArg(ExecutionArgs::Type::Silent); - context.Args.AddArg(ExecutionArgs::Type::Log, "MyLog.log"); - context.Args.AddArg(ExecutionArgs::Type::InstallLocation, "MyDir"); - context.Args.AddArg(ExecutionArgs::Type::Override, "/OverrideEverything"); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"); + context.Args.AddArg(Execution::Args::Type::Override, "/OverrideEverything"); ShellExecuteInstallerHandlerTest testhandler(manifest.Installers.at(0), context); std::string installerArgs = testhandler.TestInstallerArgs(); REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch @@ -351,8 +351,8 @@ TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow]") TestCommon::TempFile installResultPath("TestExeInstalled.txt"); std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnOne"); + Execution::Context context{ installOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnOne"); InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -370,8 +370,8 @@ TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow]") TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnZero"); + Execution::Context context{ installOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnZero"); InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -383,8 +383,8 @@ TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow]") TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow]") { std::ostringstream installOutput; - ExecutionContext context{ installOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnTwo"); + Execution::Context context{ installOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnTwo"); InstallFlowTest testFlow(context); testFlow.Execute(); INFO(installOutput.str()); @@ -396,8 +396,8 @@ TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow]") TEST_CASE("InstallFlow_SearchAndShowAppInfo", "[ShowFlow]") { std::ostringstream showOutput; - ExecutionContext context{ showOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnOne"); + Execution::Context context{ showOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnOne"); ShowFlowTest testFlow(context); testFlow.Execute(); INFO(showOutput.str()); @@ -412,9 +412,9 @@ TEST_CASE("InstallFlow_SearchAndShowAppInfo", "[ShowFlow]") TEST_CASE("InstallFlow_SearchAndShowAppVersion", "[ShowFlow]") { std::ostringstream showOutput; - ExecutionContext context{ showOutput, std::cin }; - context.Args.AddArg(ExecutionArgs::Type::Query, "TestQueryReturnOne"); - context.Args.AddArg(ExecutionArgs::Type::ListVersions); + Execution::Context context{ showOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnOne"); + context.Args.AddArg(Execution::Args::Type::ListVersions); ShowFlowTest testFlow(context); testFlow.Execute(); INFO(showOutput.str()); diff --git a/src/AppInstallerCommonCore/Public/AppInstallerProgress.h b/src/AppInstallerCommonCore/Public/AppInstallerProgress.h @@ -26,15 +26,22 @@ namespace AppInstaller Percent, }; - // Callback interface given to the worker to report to. - // Also enables the caller to request cancellation. - struct IProgressCallback + // Interface that only receives progress, and does not participate in cancellation. + // This allows a sink be simple, and let ProgressCallback handle the complications + // of cancel state. + struct IProgressSink { - using CancelFunctionRemoval = wil::unique_any<IProgressCallback*, decltype(&details::RemoveCancellationFunction), details::RemoveCancellationFunction>; // Called as progress is made. // If maximum is 0, the maximum is unknown. virtual void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) = 0; + }; + + // Callback interface given to the worker to report to. + // Also enables the caller to request cancellation. + struct IProgressCallback : public IProgressSink + { + using CancelFunctionRemoval = wil::unique_any<IProgressCallback*, decltype(&details::RemoveCancellationFunction), details::RemoveCancellationFunction>; // Returns a value indicating if the future has been cancelled. virtual bool IsCancelled() = 0; @@ -47,14 +54,14 @@ namespace AppInstaller struct ProgressCallback : public IProgressCallback { ProgressCallback() = default; - ProgressCallback(IProgressCallback* callback) : m_callback(callback) {} + ProgressCallback(IProgressSink* sink) : m_sink(sink) {} void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override { - IProgressCallback* callback = GetCallback(); - if (callback) + IProgressSink* sink = GetSink(); + if (sink) { - callback->OnProgress(current, maximum, type); + sink->OnProgress(current, maximum, type); } } @@ -85,13 +92,13 @@ namespace AppInstaller } } - IProgressCallback* GetCallback() + IProgressSink* GetSink() { - return m_callback.load(); + return m_sink.load(); } private: - std::atomic<IProgressCallback*> m_callback = nullptr; + std::atomic<IProgressSink*> m_sink = nullptr; std::atomic_bool m_cancelled = false; std::function<void()> m_cancellationFunction; };