ResumeCommand.cpp (5507B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "AppInstallerRuntime.h" 5 #include "Resources.h" 6 #include "ResumeCommand.h" 7 #include "RootCommand.h" 8 #include "CheckpointManager.h" 9 #include "Workflows/ResumeFlow.h" 10 11 using namespace AppInstaller::Checkpoints; 12 13 namespace AppInstaller::CLI 14 { 15 namespace 16 { 17 std::unique_ptr<Command> FindCommandToResume(const std::string& commandFullName) 18 { 19 std::unique_ptr<Command> commandToResume = std::make_unique<RootCommand>(); 20 21 for (const auto& commandPart : Utility::Split(commandFullName, ':')) 22 { 23 bool commandFound = false; 24 if (Utility::CaseInsensitiveEquals(commandPart, commandToResume->Name())) 25 { 26 // Since we always expect to start at the 'root' command, skip and check the next command part. 27 continue; 28 } 29 30 for (auto& command : commandToResume->GetCommands()) 31 { 32 if (Utility::CaseInsensitiveEquals(commandPart, command->Name())) 33 { 34 commandFound = true; 35 commandToResume = std::move(command); 36 break; 37 } 38 } 39 40 if (!commandFound) 41 { 42 THROW_HR_MSG(E_UNEXPECTED, "Command to resume not found."); 43 } 44 } 45 46 return std::move(commandToResume); 47 } 48 } 49 50 using namespace std::string_view_literals; 51 using namespace Execution; 52 53 std::vector<Argument> ResumeCommand::GetArguments() const 54 { 55 return { 56 Argument::ForType(Execution::Args::Type::ResumeId), 57 Argument::ForType(Execution::Args::Type::IgnoreResumeLimit), 58 }; 59 } 60 61 Resource::LocString ResumeCommand::ShortDescription() const 62 { 63 return { Resource::String::ResumeCommandShortDescription }; 64 } 65 66 Resource::LocString ResumeCommand::LongDescription() const 67 { 68 return { Resource::String::ResumeCommandLongDescription }; 69 } 70 71 Utility::LocIndView ResumeCommand::HelpLink() const 72 { 73 return "https://aka.ms/winget-command-resume"_liv; 74 } 75 76 void ResumeCommand::ExecuteInternal(Execution::Context& context) const 77 { 78 const auto& resumeId = context.Args.GetArg(Execution::Args::Type::ResumeId); 79 80 if (!std::filesystem::exists(Checkpoints::CheckpointManager::GetCheckpointDatabasePath(resumeId))) 81 { 82 context.Reporter.Error() << Resource::String::ResumeIdNotFoundError(Utility::LocIndView{ resumeId }) << std::endl; 83 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND); 84 } 85 86 Execution::Context resumeContext = context.CreateEmptyContext(); 87 std::optional<Checkpoint<AutomaticCheckpointData>> foundAutomaticCheckpoint = resumeContext.LoadCheckpoint(std::string{ resumeId }); 88 if (!foundAutomaticCheckpoint.has_value()) 89 { 90 context.Reporter.Error() << Resource::String::ResumeStateDataNotFoundError << std::endl; 91 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE); 92 } 93 94 Checkpoint<AutomaticCheckpointData> automaticCheckpoint = foundAutomaticCheckpoint.value(); 95 96 const auto& checkpointClientVersion = automaticCheckpoint.Get(AutomaticCheckpointData::ClientVersion, {}); 97 if (checkpointClientVersion != AppInstaller::Runtime::GetClientVersion().get()) 98 { 99 context.Reporter.Error() << Resource::String::ClientVersionMismatchError(Utility::LocIndView{ checkpointClientVersion }) << std::endl; 100 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH); 101 } 102 103 const auto& resumeCountString = automaticCheckpoint.Get(AutomaticCheckpointData::ResumeCount, {}); 104 int resumeCount = std::stoi(resumeCountString); 105 if (!context.Args.Contains(Execution::Args::Type::IgnoreResumeLimit) && resumeCount >= Settings::User().Get<Settings::Setting::MaxResumes>()) 106 { 107 std::string manualResumeString = "winget resume -g " + std::string{ resumeId } + " --ignore-resume-limit"; 108 context.Reporter.Error() << Resource::String::ResumeLimitExceeded(Utility::LocIndView{ resumeCountString }) << Utility::LocIndView{ manualResumeString } << std::endl; 109 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED); 110 } 111 else 112 { 113 automaticCheckpoint.Update(AutomaticCheckpointData::ResumeCount, {}, std::to_string(resumeCount + 1)); 114 } 115 116 const auto& checkpointCommand = automaticCheckpoint.Get(AutomaticCheckpointData::Command, {}); 117 AICLI_LOG(CLI, Info, << "Resuming command: " << checkpointCommand); 118 std::unique_ptr<Command> commandToResume = FindCommandToResume(checkpointCommand); 119 120 LoadCommandArgsFromAutomaticCheckpoint(resumeContext, automaticCheckpoint); 121 122 resumeContext.SetExecutingCommand(commandToResume.get()); 123 124 // TODO: Ensure telemetry is properly handled for resume context. 125 resumeContext.SetFlags(Execution::ContextFlag::Resume); 126 127 auto previousThreadGlobals = resumeContext.SetForCurrentThread(); 128 resumeContext.EnableSignalTerminationHandler(); 129 commandToResume->Resume(resumeContext); 130 context.SetTerminationHR(resumeContext.GetTerminationHR()); 131 } 132 }