Skip to content

Commit

Permalink
Add resume command and support saving the argument state. (#3508)
Browse files Browse the repository at this point in the history
  • Loading branch information
ryfu-msft authored Oct 12, 2023
1 parent 7ca4297 commit 85951ae
Show file tree
Hide file tree
Showing 57 changed files with 1,907 additions and 15 deletions.
1 change: 1 addition & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ https
HWND
Hyperlink
IAppx
ICheckpoint
IConfiguration
icu
IDX
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@
<ItemGroup>
<ClInclude Include="Argument.h" />
<ClInclude Include="ChannelStreams.h" />
<ClInclude Include="CheckpointManager.h" />
<ClInclude Include="Command.h" />
<ClInclude Include="Commands\COMCommand.h" />
<ClInclude Include="Commands\CompleteCommand.h" />
Expand All @@ -371,6 +372,7 @@
<ClInclude Include="Commands\ShowCommand.h" />
<ClInclude Include="Commands\InstallCommand.h" />
<ClInclude Include="Commands\RootCommand.h" />
<ClInclude Include="Commands\ResumeCommand.h" />
<ClInclude Include="Commands\SourceCommand.h" />
<ClInclude Include="Commands\TestCommand.h" />
<ClInclude Include="Commands\UninstallCommand.h" />
Expand Down Expand Up @@ -414,6 +416,7 @@
<ClInclude Include="Workflows\ShellExecuteInstallerHandler.h" />
<ClInclude Include="Workflows\InstallFlow.h" />
<ClInclude Include="Workflows\ManifestComparator.h" />
<ClInclude Include="Workflows\ResumeFlow.h" />
<ClInclude Include="Workflows\ShowFlow.h" />
<ClInclude Include="Workflows\SourceFlow.h" />
<ClInclude Include="Workflows\UninstallFlow.h" />
Expand All @@ -422,6 +425,7 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="COMContext.cpp" />
<ClCompile Include="CheckpointManager.cpp" />
<ClCompile Include="Commands\COMCommand.cpp" />
<ClCompile Include="Commands\ConfigureCommand.cpp" />
<ClCompile Include="Commands\ConfigureShowCommand.cpp" />
Expand Down Expand Up @@ -453,6 +457,7 @@
<ClCompile Include="Commands\ShowCommand.cpp" />
<ClCompile Include="Commands\InstallCommand.cpp" />
<ClCompile Include="Commands\RootCommand.cpp" />
<ClCompile Include="Commands\ResumeCommand.cpp" />
<ClCompile Include="Commands\SourceCommand.cpp" />
<ClCompile Include="Commands\UninstallCommand.cpp" />
<ClCompile Include="Commands\UpgradeCommand.cpp" />
Expand Down Expand Up @@ -484,6 +489,7 @@
<ClCompile Include="Workflows\ShellExecuteInstallerHandler.cpp" />
<ClCompile Include="Workflows\InstallFlow.cpp" />
<ClCompile Include="Workflows\ManifestComparator.cpp" />
<ClCompile Include="Workflows\ResumeFlow.cpp" />
<ClCompile Include="Workflows\ShowFlow.cpp" />
<ClCompile Include="Workflows\SourceFlow.cpp" />
<ClCompile Include="Workflows\UninstallFlow.cpp" />
Expand Down
22 changes: 20 additions & 2 deletions src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,15 @@
<ClInclude Include="Commands\ErrorCommand.h">
<Filter>Commands</Filter>
</ClInclude>
<ClInclude Include="Commands\ResumeCommand.h">
<Filter>Commands</Filter>
</ClInclude>
<ClInclude Include="Workflows\ResumeFlow.h">
<Filter>Workflows</Filter>
</ClInclude>
<ClInclude Include="CheckpointManager.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
Expand Down Expand Up @@ -424,10 +433,19 @@
<ClCompile Include="Commands\TestCommand.cpp">
<Filter>Commands</Filter>
</ClCompile>
<ClCompile Include="Commands\DownloadCommand.cpp">
<ClCompile Include="ConfigurationCommon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ConfigurationCommon.cpp">
<ClInclude Include="Commands\DownloadCommand.cpp">
<Filter>Commands</Filter>
</ClInclude>
<ClCompile Include="Commands\ResumeCommand.cpp">
<Filter>Commands</Filter>
</ClCompile>
<ClCompile Include="Workflows\ResumeFlow.cpp">
<Filter>Workflows</Filter>
</ClCompile>
<ClCompile Include="CheckpointManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Commands\ErrorCommand.cpp">
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ namespace AppInstaller::CLI
case Execution::Args::Type::ErrorInput:
return { type, "input"_liv, ArgTypeCategory::None };

// Resume command
case Execution::Args::Type::ResumeId:
return { type, "resume-id"_liv, 'g', ArgTypeCategory::None };

// Configuration commands
case Execution::Args::Type::ConfigurationFile:
return { type, "file"_liv, 'f' };
Expand Down Expand Up @@ -355,6 +359,8 @@ namespace AppInstaller::CLI
return Argument{ type, Resource::String::DownloadDirectoryArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false };
case Args::Type::InstallerType:
return Argument{ type, Resource::String::InstallerTypeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false };
case Args::Type::ResumeId:
return Argument{ type, Resource::String::ResumeIdArgumentDescription, ArgumentType::Standard, true };
default:
THROW_HR(E_UNEXPECTED);
}
Expand Down
166 changes: 166 additions & 0 deletions src/AppInstallerCLICore/CheckpointManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "CheckpointManager.h"
#include "Command.h"
#include "ExecutionContextData.h"
#include <AppInstallerRuntime.h>

namespace AppInstaller::Checkpoints
{
using namespace AppInstaller::CLI;
using namespace AppInstaller::Repository::Microsoft;
using namespace AppInstaller::Repository::SQLite;

// This checkpoint name is reserved for the starting checkpoint which captures the automatic metadata.
constexpr std::string_view s_AutomaticCheckpoint = "automatic"sv;
constexpr std::string_view s_CheckpointsFileName = "checkpoints.db"sv;

std::filesystem::path CheckpointManager::GetCheckpointDatabasePath(const std::string_view& resumeId, bool createCheckpointDirectory)
{
const auto checkpointsDirectory = Runtime::GetPathTo(Runtime::PathName::CheckpointsLocation) / resumeId;

if (createCheckpointDirectory)
{
if (!std::filesystem::exists(checkpointsDirectory))
{
AICLI_LOG(Repo, Info, << "Creating checkpoint database directory: " << checkpointsDirectory);
std::filesystem::create_directories(checkpointsDirectory);
}
else
{
THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !std::filesystem::is_directory(checkpointsDirectory));
}
}

auto recordPath = checkpointsDirectory / s_CheckpointsFileName;
return recordPath;
}

CheckpointManager::CheckpointManager()
{
GUID resumeId;
std::ignore = CoCreateGuid(&resumeId);
m_resumeId = Utility::ConvertGuidToString(resumeId);
const auto& checkpointDatabasePath = GetCheckpointDatabasePath(m_resumeId, true);
m_checkpointDatabase = CheckpointDatabase::CreateNew(checkpointDatabasePath.u8string());
}

CheckpointManager::CheckpointManager(const std::string& resumeId)
{
m_resumeId = resumeId;
const auto& checkpointDatabasePath = GetCheckpointDatabasePath(m_resumeId);
m_checkpointDatabase = CheckpointDatabase::Open(checkpointDatabasePath.u8string());
}

void CheckpointManager::CreateAutomaticCheckpoint(CLI::Execution::Context& context)
{
CheckpointDatabase::IdType startCheckpointId = m_checkpointDatabase->AddCheckpoint(s_AutomaticCheckpoint);
Checkpoint<AutomaticCheckpointData> automaticCheckpoint{ m_checkpointDatabase, startCheckpointId };

automaticCheckpoint.Set(AutomaticCheckpointData::ClientVersion, {}, AppInstaller::Runtime::GetClientVersion());

const auto& executingCommand = context.GetExecutingCommand();
if (executingCommand != nullptr)
{
automaticCheckpoint.Set(AutomaticCheckpointData::Command, {}, std::string{ executingCommand->FullName() });
}

const auto& argTypes = context.Args.GetTypes();
for (auto type : argTypes)
{
const auto& argument = std::to_string(static_cast<int>(type));
auto argumentType = Argument::ForType(type).Type();

if (argumentType == ArgumentType::Flag)
{
automaticCheckpoint.Set(AutomaticCheckpointData::Arguments, argument, {});
}
else
{
const auto& values = *context.Args.GetArgs(type);
automaticCheckpoint.SetMany(AutomaticCheckpointData::Arguments, argument, values);
}
}
}

void LoadCommandArgsFromAutomaticCheckpoint(CLI::Execution::Context& context, Checkpoint<AutomaticCheckpointData>& automaticCheckpoint)
{
for (const auto& fieldName : automaticCheckpoint.GetFieldNames(AutomaticCheckpointData::Arguments))
{
// Command arguments are represented as integer strings in the checkpoint record.
Execution::Args::Type type = static_cast<Execution::Args::Type>(std::stoi(fieldName));
auto argumentType = Argument::ForType(type).Type();
if (argumentType == ArgumentType::Flag)
{
context.Args.AddArg(type);
}
else
{
const auto& values = automaticCheckpoint.GetMany(AutomaticCheckpointData::Arguments, fieldName);
for (const auto& value : values)
{
context.Args.AddArg(type, value);
}
}
}
}

std::optional<Checkpoint<AutomaticCheckpointData>> CheckpointManager::GetAutomaticCheckpoint()
{
const auto& checkpointIds = m_checkpointDatabase->GetCheckpointIds();
if (checkpointIds.empty())
{
return {};
}

CheckpointDatabase::IdType automaticCheckpointId = checkpointIds.back();
return Checkpoint<AutomaticCheckpointData>{ m_checkpointDatabase, automaticCheckpointId };
}

Checkpoint<CLI::Execution::Data> CheckpointManager::CreateCheckpoint(std::string_view checkpointName)
{
CheckpointDatabase::IdType checkpointId = m_checkpointDatabase->AddCheckpoint(checkpointName);
Checkpoint<CLI::Execution::Data> checkpoint{ m_checkpointDatabase, checkpointId };
return checkpoint;
}

std::vector<Checkpoint<CLI::Execution::Data>> CheckpointManager::GetCheckpoints()
{
auto checkpointIds = m_checkpointDatabase->GetCheckpointIds();
if (checkpointIds.empty())
{
return {};
}

// Remove the last checkpoint (automatic)
checkpointIds.pop_back();

std::vector<Checkpoint<CLI::Execution::Data>> checkpoints;
for (const auto& checkpointId : checkpointIds)
{
checkpoints.emplace_back(Checkpoint<CLI::Execution::Data>{ m_checkpointDatabase, checkpointId });
}

return checkpoints;
}

void CheckpointManager::CleanUpDatabase()
{
if (m_checkpointDatabase)
{
m_checkpointDatabase.reset();
}

if (!m_resumeId.empty())
{
const auto& checkpointDatabasePath = GetCheckpointDatabasePath(m_resumeId);
if (std::filesystem::exists(checkpointDatabasePath))
{
const auto& checkpointDatabaseParentDirectory = checkpointDatabasePath.parent_path();
AICLI_LOG(CLI, Info, << "Deleting Checkpoint database directory: " << checkpointDatabaseParentDirectory);
std::filesystem::remove_all(checkpointDatabaseParentDirectory);
}
}
}
}
52 changes: 52 additions & 0 deletions src/AppInstallerCLICore/CheckpointManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "ExecutionContextData.h"
#include "ExecutionContext.h"
#include "Public/winget/Checkpoint.h"
#include <guiddef.h>

namespace AppInstaller::Repository::Microsoft
{
struct CheckpointDatabase;
}

namespace AppInstaller::Checkpoints
{
// Reads the command arguments from the automatic checkpoint and populates the context.
void LoadCommandArgsFromAutomaticCheckpoint(CLI::Execution::Context& context, Checkpoint<AutomaticCheckpointData>& automaticCheckpoint);

// Owns the lifetime of a checkpoint data base and creates the checkpoints.
struct CheckpointManager
{
// Constructor that generates a new resume id and creates the checkpoint database.
CheckpointManager();

// Constructor that loads the resume id and opens an existing checkpoint database.
CheckpointManager(const std::string& resumeId);

~CheckpointManager() = default;

// Gets the file path of the checkpoint database.
static std::filesystem::path GetCheckpointDatabasePath(const std::string_view& resumeId, bool createCheckpointDirectory = false);

// Gets the automatic checkpoint.
std::optional<Checkpoint<AutomaticCheckpointData>> GetAutomaticCheckpoint();

// Creates an automatic checkpoint using the provided context.
void CreateAutomaticCheckpoint(CLI::Execution::Context& context);

// Gets all context data checkpoints.
std::vector<Checkpoint<CLI::Execution::Data>> GetCheckpoints();

// Creates a new context data checkpoint.
Checkpoint<CLI::Execution::Data> CreateCheckpoint(std::string_view checkpointName);

// Cleans up the checkpoint database.
void CleanUpDatabase();

private:
std::string m_resumeId;
std::shared_ptr<AppInstaller::Repository::Microsoft::CheckpointDatabase> m_checkpointDatabase;
};
}
8 changes: 7 additions & 1 deletion src/AppInstallerCLICore/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ namespace AppInstaller::CLI

void Command::Complete(Execution::Context&, Execution::Args::Type) const
{
// Derived commands must suppy context sensitive argument values.
// Derived commands must supply context sensitive argument values.
}

void Command::Execute(Execution::Context& context) const
Expand Down Expand Up @@ -883,6 +883,12 @@ namespace AppInstaller::CLI
}
}

void Command::Resume(Execution::Context& context) const
{
context.Reporter.Error() << Resource::String::CommandDoesNotSupportResumeMessage << std::endl;
AICLI_TERMINATE_CONTEXT(E_NOTIMPL);
}

void Command::SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value)
{
m_selectCurrentCommandIfUnrecognizedSubcommandFound = value;
Expand Down
3 changes: 3 additions & 0 deletions src/AppInstallerCLICore/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ namespace AppInstaller::CLI

virtual void Execute(Execution::Context& context) const;

virtual void Resume(Execution::Context& context) const;

protected:
void SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value);

Expand All @@ -127,6 +129,7 @@ namespace AppInstaller::CLI
Settings::TogglePolicy::Policy m_groupPolicy;
CommandOutputFlags m_outputFlags;
bool m_selectCurrentCommandIfUnrecognizedSubcommandFound = false;
std::string m_commandArguments;
};

template <typename Container>
Expand Down
Loading

0 comments on commit 85951ae

Please sign in to comment.