winget-cli

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

commit 6bbe990d102a705d2040aa4468b339c5e7534c9e
parent 20183b77b032f5825c280c264bcbdd8059c55622
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Mon, 24 Feb 2020 17:58:36 -0800

Future (#42)


Diffstat:
Msrc/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp | 75+++++++++------------------------------------------------------------------
Msrc/AppInstallerCLICore/Workflows/InstallerHandlerBase.h | 22+---------------------
Msrc/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp | 56++++++++++----------------------------------------------
Msrc/AppInstallerCLICore/Workflows/MsixInstallerHandler.h | 4++--
Msrc/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp | 59+++++++++++++++++++++++++++++++++++++----------------------
Msrc/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h | 4++--
Msrc/AppInstallerCLICore/Workflows/WorkflowReporter.cpp | 115++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/AppInstallerCLICore/Workflows/WorkflowReporter.h | 23++++++++++++++---------
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 1+
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 7++++++-
Msrc/AppInstallerCLITests/Downloader.cpp | 35+++++++++++++----------------------
Asrc/AppInstallerCLITests/Future.cpp | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/WorkFlow.cpp | 8++++----
Msrc/AppInstallerCLITests/pch.h | 1+
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 3+++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters | 9+++++++++
Asrc/AppInstallerCommonCore/Deployment.cpp | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Downloader.cpp | 107+++++++++++++++++--------------------------------------------------------------
Asrc/AppInstallerCommonCore/Public/AppInstallerDeployment.h | 14++++++++++++++
Msrc/AppInstallerCommonCore/Public/AppInstallerDownloader.h | 69+++++++++------------------------------------------------------------
Asrc/AppInstallerCommonCore/Public/AppInstallerFuture.h | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Public/AppInstallerLogging.h | 1+
Msrc/AppInstallerCommonCore/pch.h | 6+++---
23 files changed, 614 insertions(+), 391 deletions(-)

diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.cpp @@ -18,20 +18,16 @@ namespace AppInstaller::Workflow AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); - auto downloader = Downloader::StartDownloadAsync( + auto future = DownloadAsync( m_manifestInstallerRef.Url, tempInstallerPath, - true, - &m_downloaderCallback); + true); - auto downloadResult = downloader->Wait(); + future.SetProgressReceiver(&m_reporterRef); - if (downloadResult == DownloaderResult::Failed) - { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Package download failed."); - THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Package download failed"); - } - else if (downloadResult == DownloaderResult::Canceled) + auto hash = future.Get(); + + if (!hash) { m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Package download canceled."); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Package download canceled"); @@ -40,13 +36,13 @@ namespace AppInstaller::Workflow if (!std::equal( m_manifestInstallerRef.Sha256.begin(), m_manifestInstallerRef.Sha256.end(), - downloader->GetDownloadHash().begin())) + hash.value().begin())) { AICLI_LOG(CLI, Error, << "Package hash verification failed. SHA256 in manifest: " << SHA256::ConvertToString(m_manifestInstallerRef.Sha256) << " SHA256 from download: " - << SHA256::ConvertToString(downloader->GetDownloadHash())); + << SHA256::ConvertToString(hash.value())); if (!m_reporterRef.PromptForBoolResponse(WorkflowReporter::Level::Warning, "Package hash verification failed. Continue?")) { @@ -62,56 +58,4 @@ namespace AppInstaller::Workflow m_downloadedInstaller = tempInstallerPath; } - - void InstallerHandlerBase::DownloaderCallback::OnStarted(LONGLONG totalBytes) - { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Starting installer download ..."); - m_useProgressBar = totalBytes > 0; - - if (m_useProgressBar) - { - m_reporterRef.ShowProgress(true, 0); - } - else - { - m_reporterRef.ShowIndefiniteProgress(true); - } - } - - void InstallerHandlerBase::DownloaderCallback::OnProgress(LONGLONG bytesDownloaded, LONGLONG totalBytes) - { - if (m_useProgressBar) - { - int progressPercent = static_cast<int>(100 * bytesDownloaded / totalBytes); - m_reporterRef.ShowProgress(true, progressPercent); - } - } - - void InstallerHandlerBase::DownloaderCallback::OnCanceled() - { - if (m_useProgressBar) - { - m_reporterRef.ShowProgress(false, 0); - } - else - { - m_reporterRef.ShowIndefiniteProgress(false); - } - - m_reporterRef.ShowMsg(WorkflowReporter::Level::Warning, "Installer download canceled."); - } - - void InstallerHandlerBase::DownloaderCallback::OnCompleted() - { - if (m_useProgressBar) - { - m_reporterRef.ShowProgress(false, 0); - } - else - { - m_reporterRef.ShowIndefiniteProgress(false); - } - - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Installer download completed."); - } -}- \ No newline at end of file +} diff --git a/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h b/src/AppInstallerCLICore/Workflows/InstallerHandlerBase.h @@ -29,36 +29,16 @@ namespace AppInstaller::Workflow virtual void Cancel() { THROW_HR(E_NOTIMPL); } protected: - - // This will be triggered by file downloader to report download progress - class DownloaderCallback : public AppInstaller::Utility::IDownloaderCallback - { - public: - DownloaderCallback(WorkflowReporter& reporter) : m_reporterRef(reporter) {}; - - void OnStarted(LONGLONG totalBytes) override; - void OnProgress(LONGLONG bytesDownloaded, LONGLONG totalBytes) override; - void OnCanceled() override; - void OnCompleted() override; - - private: - WorkflowReporter& m_reporterRef; - - // This determines if definite progress bar or indefinite progress bar should be shown. - bool m_useProgressBar = true; - }; - InstallerHandlerBase( const Manifest::ManifestInstaller& manifestInstaller, const CLI::Invocation& args, WorkflowReporter& reporter) : - m_manifestInstallerRef(manifestInstaller), m_reporterRef(reporter), m_downloaderCallback(reporter), m_argsRef(args) {}; + m_manifestInstallerRef(manifestInstaller), m_reporterRef(reporter), m_argsRef(args) {}; const Manifest::ManifestInstaller& m_manifestInstallerRef; const CLI::Invocation& m_argsRef; WorkflowReporter& m_reporterRef; std::filesystem::path m_downloadedInstaller; - DownloaderCallback m_downloaderCallback; }; } diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.cpp @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #include "pch.h" #include "Common.h" #include "MsixInstallerHandler.h" +#include <AppInstallerDeployment.h> using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Management::Deployment; @@ -63,57 +63,21 @@ namespace AppInstaller::Workflow THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), "Installer not downloaded yet"); } - auto installTask = ExecuteInstallerAsync( - m_useStreaming ? Uri(Utility::ConvertToUTF16(m_manifestInstallerRef.Url)) : Uri(m_downloadedInstaller.c_str())); + Uri target = m_useStreaming ? Uri(Utility::ConvertToUTF16(m_manifestInstallerRef.Url)) : Uri(m_downloadedInstaller.c_str()); + + auto installTask = ExecuteInstallerAsync(target); + installTask.SetProgressReceiver(&m_reporterRef); - installTask.get(); + m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Starting package install..."); + installTask.Get(); + m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully installed."); } - std::future<void> MsixInstallerHandler::ExecuteInstallerAsync(const Uri& uri) + Future<void> MsixInstallerHandler::ExecuteInstallerAsync(const winrt::Windows::Foundation::Uri& uri) { - PackageManager packageManager; DeploymentOptions deploymentOptions = DeploymentOptions::ForceApplicationShutdown | DeploymentOptions::ForceTargetApplicationShutdown; - - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Starting package install..."); - m_reporterRef.ShowProgress(true, 0); - - // RequestAddPackageAsync will invoke smart screen. - auto deployOperation = packageManager.RequestAddPackageAsync( - uri, - nullptr, /*dependencyPackageUris*/ - deploymentOptions, - nullptr, /*targetVolume*/ - nullptr, /*optionalAndRelatedPackageFamilyNames*/ - nullptr /*relatedPackageUris*/); - - AsyncOperationProgressHandler<DeploymentResult, DeploymentProgress> progressCallback( - [this](const IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress>&, DeploymentProgress progress) - { - // Todo: might need to tweak progress reporting logic to account - // for the time before DeploymentRequest is dequeued. - m_reporterRef.ShowProgress(true, progress.percentage); - } - ); - - // Set progress callback. - deployOperation.Progress(progressCallback); - - co_await deployOperation; - - auto deployResult = deployOperation.GetResults(); - - m_reporterRef.ShowProgress(false, 0); - - if (!SUCCEEDED(deployResult.ExtendedErrorCode())) - { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Install failed. Reason: " + Utility::ConvertToUTF8(deployResult.ErrorText())); - - THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), - "Install failed. Installer task returned: %u", deployResult.ExtendedErrorCode()); - } - - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully installed."); + return Deployment::RequestAddPackageAsync(uri, deploymentOptions); } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MsixInstallerHandler.h @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #pragma once #include "InstallerHandlerBase.h" +#include <AppInstallerFuture.h> namespace AppInstaller::Workflow { @@ -27,6 +27,6 @@ namespace AppInstaller::Workflow // If use streaming install vs download install. bool m_useStreaming = true; - virtual std::future<void> ExecuteInstallerAsync(const winrt::Windows::Foundation::Uri& uri); + virtual Future<void> ExecuteInstallerAsync(const winrt::Windows::Foundation::Uri& uri); }; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #include "pch.h" #include "Common.h" #include "Commands/Common.h" @@ -24,31 +23,33 @@ namespace AppInstaller::Workflow AICLI_LOG(CLI, Info, << "Installer args: " << installerArgs); RenameDownloadedInstaller(); - std::future<DWORD> installTask = ExecuteInstallerAsync(m_downloadedInstaller, installerArgs); - - m_reporterRef.ShowIndefiniteProgress(true); - - installTask.wait(); - - m_reporterRef.ShowIndefiniteProgress(false); - auto installResult = installTask.get(); + Future<DWORD> installTask = ExecuteInstallerAsync(m_downloadedInstaller, installerArgs); + installTask.SetProgressReceiver(&m_reporterRef); + auto installResult = installTask.Get(); - if (installResult != 0) + if (!installResult) { - m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Install failed. Exit code: " + std::to_string(installResult)); + m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Installation cancelled"); + } + else if (installResult.value() != 0) + { + m_reporterRef.ShowMsg(WorkflowReporter::Level::Error, "Install failed. Exit code: " + std::to_string(installResult.value())); THROW_EXCEPTION_MSG(WorkflowException(APPINSTALLER_CLI_ERROR_INSTALLFLOW_FAILED), - "Install failed. Installer task returned: %u", installResult); + "Install failed. Installer task returned: %u", installResult.value()); + } + else + { + m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully installed!"); } - - m_reporterRef.ShowMsg(WorkflowReporter::Level::Info, "Successfully installed!"); } - std::future<DWORD> ShellExecuteInstallerHandler::ExecuteInstallerAsync(const std::filesystem::path& filePath, const std::string& args) + Future<DWORD> ShellExecuteInstallerHandler::ExecuteInstallerAsync(const std::filesystem::path& filePath, const std::string& args) { AICLI_LOG(CLI, Info, << "Staring installer. Path: " << filePath); - return std::async(std::launch::async, [this, filePath, args] + int showValue = m_argsRef.Contains(CLI::ARG_INTERACTIVE) ? SW_SHOW : SW_HIDE; + return Future<DWORD>([filePath, args, showValue] (IPromiseKeeperProgress* progress) { SHELLEXECUTEINFOA execInfo = { 0 }; execInfo.cbSize = sizeof(SHELLEXECUTEINFO); @@ -56,20 +57,34 @@ namespace AppInstaller::Workflow std::string filePathUTF8Str = Utility::ConvertToUTF8(filePath.c_str()); execInfo.lpFile = filePathUTF8Str.c_str(); execInfo.lpParameters = args.c_str(); - execInfo.nShow = m_argsRef.Contains(CLI::ARG_INTERACTIVE) ? SW_SHOW : SW_HIDE; + execInfo.nShow = showValue; if (!ShellExecuteExA(&execInfo) || !execInfo.hProcess) { return GetLastError(); } + + wil::unique_process_handle process{ execInfo.hProcess }; // Wait for installation to finish - WaitForSingleObject(execInfo.hProcess, INFINITE); + while (!progress->IsCancelled()) + { + DWORD waitResult = WaitForSingleObject(process.get(), 250); + if (waitResult == WAIT_OBJECT_0) + { + break; + } + if (waitResult != WAIT_TIMEOUT) + { + THROW_LAST_ERROR_MSG("Unexpected WaitForSingleObjectResult: %d", waitResult); + } + } - // Get exe exit code - DWORD exitCode; - GetExitCodeProcess(execInfo.hProcess, &exitCode); + DWORD exitCode = 0; - CloseHandle(execInfo.hProcess); + if (!progress->IsCancelled()) + { + GetExitCodeProcess(process.get(), &exitCode); + } return exitCode; }); diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #pragma once #include "InstallerHandlerBase.h" +#include <AppInstallerFuture.h> namespace AppInstaller::Workflow { @@ -21,7 +21,7 @@ namespace AppInstaller::Workflow void Install() override; protected: - std::future<DWORD> ExecuteInstallerAsync(const std::filesystem::path& filePath, const std::string& args); + Future<DWORD> ExecuteInstallerAsync(const std::filesystem::path& filePath, const std::string& args); // The known default arg format if the corresponding arg is not specified in the manifest // i.e. If silent switch is not specified in manifest and installer type is msi, /quiet will be returned. diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp b/src/AppInstallerCLICore/Workflows/WorkflowReporter.cpp @@ -6,49 +6,21 @@ namespace AppInstaller::Workflow { - bool WorkflowReporter::PromptForBoolResponse(Level level, const std::string& msg) - { - UNREFERENCED_PARAMETER(level); - - out << msg << " (Y|N)" << std::endl; - - char response; - in.get(response); - - return tolower(response) == 'y'; - } - - void WorkflowReporter::ShowMsg(Level level, const std::string& msg) - { - UNREFERENCED_PARAMETER(level); - - // Todo: color output using level and possibly other factors. - out << msg << std::endl; - } - - void WorkflowReporter::ShowIndefiniteProgress(bool running) + void IndefiniteSpinner::ShowSpinner() { - if (running) - { - m_spinner.ShowSpinner(); - } - else + if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) { - m_spinner.StopSpinner(); + m_spinnerRunning = true; + m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); } } - void WorkflowReporter::ShowProgress(bool running, int progress) - { - m_progressBar.ShowProgress(running, progress); - } - - void IndefiniteSpinner::ShowSpinner() + void IndefiniteSpinner::StopSpinner() { - if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) + if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) { - m_spinnerRunning = true; - m_spinnerJob = std::async(std::launch::async, &IndefiniteSpinner::ShowSpinnerInternal, this); + m_canceled = true; + m_spinnerJob.get(); } } @@ -72,16 +44,7 @@ namespace AppInstaller::Workflow m_spinnerRunning = false; } - void IndefiniteSpinner::StopSpinner() - { - if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) - { - m_canceled = true; - m_spinnerJob.wait(); - } - } - - void ProgressBar::ShowProgress(bool running, int progress) + void ProgressBar::ShowProgress(bool running, uint64_t progress) { if (running) { @@ -104,4 +67,61 @@ namespace AppInstaller::Workflow } } } -}- \ No newline at end of file + + bool WorkflowReporter::PromptForBoolResponse(Level level, const std::string& msg) + { + UNREFERENCED_PARAMETER(level); + + out << msg << " (Y|N)" << std::endl; + + char response; + in.get(response); + + return tolower(response) == 'y'; + } + + void WorkflowReporter::ShowMsg(Level level, const std::string& msg) + { + UNREFERENCED_PARAMETER(level); + + // Todo: color output using level and possibly other factors. + out << msg << std::endl; + } + + void WorkflowReporter::ShowProgress(bool running, uint64_t progress) + { + m_progressBar.ShowProgress(running, progress); + } + + void WorkflowReporter::ShowIndefiniteProgress(bool running) + { + if (running) + { + m_spinner.ShowSpinner(); + } + else + { + m_spinner.StopSpinner(); + } + } + + // TODO: Better handling of generic progress facility + void WorkflowReporter::OnStarted() + { + ShowIndefiniteProgress(true); + } + + void WorkflowReporter::OnProgress(uint64_t current, uint64_t maximum, FutureProgressType type) + { + UNREFERENCED_PARAMETER(type); + ShowIndefiniteProgress(false); + ShowProgress(true, (maximum ? static_cast<uint64_t>((static_cast<double>(current) / maximum) * 100) : current)); + } + + void WorkflowReporter::OnCompleted(bool cancelled) + { + UNREFERENCED_PARAMETER(cancelled); + ShowIndefiniteProgress(false); + ShowProgress(false, 0); + } +} diff --git a/src/AppInstallerCLICore/Workflows/WorkflowReporter.h b/src/AppInstallerCLICore/Workflows/WorkflowReporter.h @@ -1,11 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #pragma once +#include "AppInstallerFuture.h" -#include "Manifest/Manifest.h" -#include "AppInstallerDownloader.h" -#include "Public/AppInstallerRepositorySearch.h" +#include <atomic> +#include <future> +#include <istream> +#include <ostream> +#include <string> namespace AppInstaller::Workflow { @@ -33,7 +35,7 @@ namespace AppInstaller::Workflow public: ProgressBar(std::ostream& stream) : out(stream) {}; - void ShowProgress(bool running, int progress); + void ShowProgress(bool running, uint64_t progress); private: std::atomic<bool> m_isVisible = false; @@ -42,10 +44,8 @@ namespace AppInstaller::Workflow // 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 - class WorkflowReporter + struct WorkflowReporter : public IFutureProgress { - public: - enum class Level { Verbose, @@ -63,13 +63,18 @@ namespace AppInstaller::Workflow // Used to show definite progress. // running: shows progress bar if set to true, dismisses progress bar if set to false - void ShowProgress(bool running, int progress); + void ShowProgress(bool running, uint64_t progress); // Used to show indefinite progress. Currently an indefinite spinner is the form of // showing indefinite progress. // running: shows indefinite progress if set to true, stops indefinite progress if set to false void ShowIndefiniteProgress(bool running); + // IFutureProgress + void OnStarted() override; + void OnProgress(uint64_t current, uint64_t maximum, FutureProgressType type) override; + void OnCompleted(bool cancelled) override; + private: std::ostream& out; std::istream& in; diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -152,6 +152,7 @@ </ItemGroup> <ItemGroup> <ClCompile Include="Downloader.cpp" /> + <ClCompile Include="Future.cpp" /> <ClCompile Include="WorkFlow.cpp" /> <ClCompile Include="LanguageUtilities.cpp" /> <ClCompile Include="main.cpp"> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -65,6 +65,9 @@ <ClCompile Include="Synchronization.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="Future.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> @@ -98,6 +101,8 @@ <CopyFileToFolders Include="TestData\InstallerArgTest_Inno_WithSwitches.yml"> <Filter>TestData</Filter> </CopyFileToFolders> - <CopyFileToFolders Include="TestData\InstallerArgTest_Msi_WithSwitches.yml" /> + <CopyFileToFolders Include="TestData\InstallerArgTest_Msi_WithSwitches.yml"> + <Filter>TestData</Filter> + </CopyFileToFolders> </ItemGroup> </Project> \ No newline at end of file diff --git a/src/AppInstallerCLITests/Downloader.cpp b/src/AppInstallerCLITests/Downloader.cpp @@ -14,17 +14,18 @@ TEST_CASE("DownloadValidFileAndVerifyHash", "[Downloader]") INFO("Using temporary file named: " << tempFile.GetPath()); // Todo: point to files from our repo when the repo goes public - auto downloader = Downloader::StartDownloadAsync("https://raw.githubusercontent.com/microsoft/msix-packaging/master/LICENSE", tempFile.GetPath(), true); + auto future = DownloadAsync("https://raw.githubusercontent.com/microsoft/msix-packaging/master/LICENSE", tempFile.GetPath(), true); - auto result = downloader->Wait(); + auto result = future.Get(); - REQUIRE(result == DownloaderResult::Success); + REQUIRE(result.has_value()); + auto resultHash = result.value(); auto expectedHash = SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); REQUIRE(std::equal( expectedHash.begin(), expectedHash.end(), - downloader->GetDownloadHash().begin())); + resultHash.begin())); REQUIRE(std::filesystem::file_size(tempFile.GetPath()) > 0); } @@ -34,21 +35,16 @@ TEST_CASE("DownloadValidFileAndCancel", "[Downloader]") TestCommon::TempFile tempFile("downloader_test"s, ".test"s); INFO("Using temporary file named: " << tempFile.GetPath()); - auto downloader = Downloader::StartDownloadAsync("https://aka.ms/win32-x64-user-stable", tempFile.GetPath(), true); + auto future = DownloadAsync("https://aka.ms/win32-x64-user-stable", tempFile.GetPath(), true); - DownloaderResult waitResult; - std::thread waitThread([&downloader, &waitResult] { waitResult = downloader->Wait(); }); + std::optional<std::vector<BYTE>> waitResult; + std::thread waitThread([&future, &waitResult] { waitResult = future.Get(); }); - DownloaderResult cancelResult; - std::thread cancelThread([&downloader, &cancelResult] { cancelResult = downloader->Cancel();}); + future.Cancel(); waitThread.join(); - cancelThread.join(); - REQUIRE(waitResult == cancelResult); - REQUIRE(waitResult == DownloaderResult::Canceled); - - REQUIRE_THROWS(downloader->GetDownloadHash()); + REQUIRE(!waitResult.has_value()); } TEST_CASE("DownloadUnreachableUrl", "[Downloader]") @@ -56,11 +52,7 @@ TEST_CASE("DownloadUnreachableUrl", "[Downloader]") TestCommon::TempFile tempFile("downloader_test"s, ".test"s); INFO("Using temporary file named: " << tempFile.GetPath()); - auto downloader = Downloader::StartDownloadAsync("https://does_not_exist.com/", tempFile.GetPath(), true); - - auto result = downloader->Wait(); + auto future = DownloadAsync("https://does_not_exist.com/", tempFile.GetPath(), true); - REQUIRE(result == DownloaderResult::Failed); - - REQUIRE_THROWS(downloader->GetDownloadHash()); -}- \ No newline at end of file + REQUIRE_THROWS_HR(future.Get(), WININET_E_NAME_NOT_RESOLVED); +} diff --git a/src/AppInstallerCLITests/Future.cpp b/src/AppInstallerCLITests/Future.cpp @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include <AppInstallerFuture.h> + +using namespace AppInstaller; + +struct FutureProgress : public IFutureProgress +{ + // IFutureProgress + void OnStarted() override + { + if (Started) + { + Started(); + } + } + + void OnProgress(uint64_t current, uint64_t maximum, FutureProgressType type) override + { + if (Progress) + { + Progress(current, maximum, type); + } + } + + void OnCompleted(bool cancelled) override + { + if (Completed) + { + Completed(cancelled); + } + } + + std::function<void()> Started; + std::function<void(uint64_t, uint64_t, FutureProgressType)> Progress; + std::function<void(bool)> Completed; +}; + +TEST_CASE("Future_Basic", "[Future]") +{ + int input = 42; + + Future<int> f{ std::packaged_task<int(IPromiseKeeperProgress*)>{ [input](IPromiseKeeperProgress*) { return input; } } }; + int result = f.Get().value(); + + REQUIRE(input == result); +} + +TEST_CASE("Future_Optional", "[Future]") +{ + int input = 42; + + Future<std::optional<int>> f{ std::packaged_task<std::optional<int>(IPromiseKeeperProgress*)>{ [input](IPromiseKeeperProgress*) { return input; } } }; + int result = f.Get().value(); + + REQUIRE(input == result); +} + +TEST_CASE("Future_Callbacks", "[Future]") +{ + int input = 42; + uint64_t progress = 110; + uint64_t maximum = 100; + FutureProgressType type = FutureProgressType::Percent; + + bool startedCalled = false; + uint64_t progressActual = 0; + uint64_t maximumActual = 0; + FutureProgressType typeActual = FutureProgressType::None; + bool cancelledActual = false; + + FutureProgress futureProgress; + futureProgress.Started = [&]() { startedCalled = true; }; + futureProgress.Progress = [&](uint64_t p, uint64_t m, FutureProgressType t) { progressActual = p; maximumActual = m; typeActual = t; }; + futureProgress.Completed = [&](bool c) { cancelledActual = c; }; + + Future<std::optional<int>> f{ std::packaged_task<std::optional<int>(IPromiseKeeperProgress*)>{ + [&](IPromiseKeeperProgress* p) { + p->OnProgress(progress, maximum, type); + return input; + } + } }; + f.SetProgressReceiver(&futureProgress); + int result = f.Get().value(); + + REQUIRE(input == result); + REQUIRE(startedCalled); + REQUIRE(progress == progressActual); + REQUIRE(maximum == maximumActual); + REQUIRE(type == typeActual); + REQUIRE(!cancelledActual); +} + +TEST_CASE("Future_Cancelled", "[Future]") +{ + int input = 42; + uint64_t progress = 110; + uint64_t maximum = 100; + FutureProgressType type = FutureProgressType::Percent; + + bool startedCalled = false; + uint64_t progressActual = 0; + uint64_t maximumActual = 0; + FutureProgressType typeActual = FutureProgressType::None; + bool cancelledActual = false; + + Future<std::optional<int>> f{ std::packaged_task<std::optional<int>(IPromiseKeeperProgress*)>{ + [&](IPromiseKeeperProgress* p) { + p->OnProgress(progress, maximum, type); + if (p->IsCancelled()) + { + return 0; + } + else + { + return input; + } + } + } }; + + FutureProgress futureProgress; + futureProgress.Started = [&]() { startedCalled = true; }; + futureProgress.Progress = [&](uint64_t p, uint64_t m, FutureProgressType t) { + progressActual = p; maximumActual = m; typeActual = t; + f.Cancel(); + }; + futureProgress.Completed = [&](bool c) { cancelledActual = c; }; + f.SetProgressReceiver(&futureProgress); + + auto result = f.Get(); + + REQUIRE(!result.has_value()); + REQUIRE(startedCalled); + REQUIRE(progress == progressActual); + REQUIRE(maximum == maximumActual); + REQUIRE(type == typeActual); + REQUIRE(cancelledActual); +} diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -17,10 +17,10 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Management::Deployment; using namespace TestCommon; -using namespace AppInstaller::Workflow; -using namespace AppInstaller::Utility; using namespace AppInstaller::Manifest; using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Workflow; class MsixInstallerHandlerTest : public MsixInstallerHandler { @@ -32,7 +32,7 @@ public: protected: - std::future<void> ExecuteInstallerAsync(const Uri& uri) override + AppInstaller::Future<void> ExecuteInstallerAsync(const Uri& uri) override { std::filesystem::path temp = std::filesystem::temp_directory_path(); temp /= "TestMsixInstalled.txt"; @@ -42,7 +42,7 @@ protected: file.close(); - co_return; + return AppInstaller::Future<void>([](AppInstaller::IPromiseKeeperProgress*) {}); } }; diff --git a/src/AppInstallerCLITests/pch.h b/src/AppInstallerCLITests/pch.h @@ -18,6 +18,7 @@ #include <atomic> #include <filesystem> #include <fstream> +#include <functional> #include <future> #include <iostream> #include <sstream> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -173,9 +173,11 @@ <ClInclude Include="HttpStream\HttpRandomAccessStream.h" /> <ClInclude Include="pch.h" /> <ClInclude Include="Public\AppInstallerDateTime.h" /> + <ClInclude Include="Public\AppInstallerDeployment.h" /> <ClInclude Include="Public\AppInstallerDownloader.h" /> <ClInclude Include="Public\AppInstallerErrors.h" /> <ClInclude Include="Public\AppInstallerFileLogger.h" /> + <ClInclude Include="Public\AppInstallerFuture.h" /> <ClInclude Include="Public\AppInstallerLanguageUtilities.h" /> <ClInclude Include="Public\AppInstallerMsixInfo.h" /> <ClInclude Include="Public\AppInstallerRuntime.h" /> @@ -193,6 +195,7 @@ <ClCompile Include="AppInstallerLogging.cpp" /> <ClCompile Include="AppInstallerStrings.cpp" /> <ClCompile Include="DateTime.cpp" /> + <ClCompile Include="Deployment.cpp" /> <ClCompile Include="Downloader.cpp" /> <ClCompile Include="FileLogger.cpp" /> <ClCompile Include="HttpStream\HttpClientWrapper.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -84,6 +84,12 @@ <ClInclude Include="Public\AppInstallerSynchronization.h"> <Filter>Public</Filter> </ClInclude> + <ClInclude Include="Public\AppInstallerFuture.h"> + <Filter>Public</Filter> + </ClInclude> + <ClInclude Include="Public\AppInstallerDeployment.h"> + <Filter>Public</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -134,6 +140,9 @@ <ClCompile Include="Synchronization.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="Deployment.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "pch.h" +#include "Public/AppInstallerDeployment.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerStrings.h" + +namespace AppInstaller::Deployment +{ + namespace + { + size_t GetDeploymentOperationId() + { + static std::atomic_size_t s_deploymentId = 0; + return s_deploymentId.fetch_add(1); + } + } + + Future<void> RequestAddPackageAsync( + const winrt::Windows::Foundation::Uri& uri, + winrt::Windows::Management::Deployment::DeploymentOptions options) + { + return Future<void>([uri, options](IPromiseKeeperProgress* pkp) + { + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Management::Deployment; + + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RequestAddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str())); + + PackageManager packageManager; + + // RequestAddPackageAsync will invoke smart screen. + auto deployOperation = packageManager.RequestAddPackageAsync( + uri, + nullptr, /*dependencyPackageUris*/ + options, + nullptr, /*targetVolume*/ + nullptr, /*optionalAndRelatedPackageFamilyNames*/ + nullptr /*relatedPackageUris*/); + + AsyncOperationProgressHandler<DeploymentResult, DeploymentProgress> progressCallback( + [pkp](const IAsyncOperationWithProgress<DeploymentResult, DeploymentProgress>&, DeploymentProgress progress) + { + pkp->OnProgress(progress.percentage, 100, FutureProgressType::Percent); + } + ); + + // Set progress callback. + deployOperation.Progress(progressCallback); + + auto deployResult = deployOperation.GetResults(); + + if (!SUCCEEDED(deployResult.ExtendedErrorCode())) + { + AICLI_LOG(Core, Error, << "Deployment failed #" << id << ": " << Utility::ConvertToUTF8(deployResult.ErrorText())); + + THROW_HR_MSG(deployResult.ExtendedErrorCode(), "Install failed: %s", Utility::ConvertToUTF8(deployResult.ErrorText()).c_str()); + } + else + { + AICLI_LOG(Core, Info, << "Successfully deployed #" << id); + } + }); + } +} diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #include "pch.h" #include "Public/AppInstallerRuntime.h" #include "Public/AppInstallerDownloader.h" @@ -12,27 +11,13 @@ using namespace AppInstaller::Runtime; namespace AppInstaller::Utility { - std::unique_ptr<Downloader> Downloader::StartDownloadAsync( - const std::string& url, - const std::filesystem::path& dest, - bool computeHash, - IDownloaderCallback* callback) - { - // std::make_unique cannot access private constructor - auto downloader = std::unique_ptr<Downloader>(new Downloader()); - - downloader->m_downloadTask = std::async(std::launch::async, &Downloader::DownloadInternal, downloader.get(), url, dest, computeHash, callback); - - return downloader; - } - - DownloaderResult Downloader::DownloadInternal( - const std::string& url, - const std::filesystem::path& dest, - bool computeHash, - IDownloaderCallback* callback) + namespace { - try + std::vector<BYTE> DownloadAsyncInternal( + IPromiseKeeperProgress* progress, + const std::string& url, + const std::filesystem::path& dest, + bool computeHash) { AICLI_LOG(CLI, Info, << "Downloading url: " << url << " , dest: " << dest); @@ -96,21 +81,12 @@ namespace AppInstaller::Utility DWORD bytesRead = 0; LONGLONG bytesDownloaded = 0; - if (callback) - { - callback->OnStarted(contentLength); - } - do { - if (m_cancelled) + if (progress->IsCancelled()) { - if (callback) - { - callback->OnCanceled(); - } - - return DownloaderResult::Canceled; + AICLI_LOG(CLI, Info, << "Download cancelled."); + return {}; } readSuccess = InternetReadFile(urlFile.get(), buffer.get(), bufferSize, &bytesRead); @@ -126,74 +102,35 @@ namespace AppInstaller::Utility bytesDownloaded += bytesRead; - if (callback && bytesRead != 0) + if (bytesRead != 0) { - callback->OnProgress(bytesDownloaded, contentLength); + progress->OnProgress(bytesDownloaded, contentLength, FutureProgressType::Bytes); } } while (bytesRead != 0); outfile.flush(); + std::vector<BYTE> result; if (computeHash) { - m_downloadHash = hashEngine.Get(); - AICLI_LOG(CLI, Info, << "Download hash: " << SHA256::ConvertToString(m_downloadHash)); - } - - if (callback) - { - callback->OnCompleted(); + result = hashEngine.Get(); + AICLI_LOG(CLI, Info, << "Download hash: " << SHA256::ConvertToString(result)); } AICLI_LOG(CLI, Info, << "Download completed."); - return DownloaderResult::Success; - } - catch (const wil::ResultException& e) - { - AICLI_LOG(Fail, Error, << "Download failed. HResult: " << e.GetErrorCode() << " Reason: " << e.what()); - return DownloaderResult::Failed; - } - catch (const std::exception& e) - { - AICLI_LOG(Fail, Error, << "Download failed. Reason: " << e.what()); - return DownloaderResult::Failed; - } - } - - DownloaderResult Downloader::Cancel() - { - if (!m_downloadTask.valid()) - { - THROW_HR_MSG(E_UNEXPECTED, "No active download found. Cancel failed."); - } - - if (!m_cancelled) - { - m_cancelled = true; + return result; } - - return m_downloadTask.get(); } - DownloaderResult Downloader::Wait() + Future<std::vector<BYTE>> DownloadAsync( + const std::string& url, + const std::filesystem::path& dest, + bool computeHash) { - if (!m_downloadTask.valid()) - { - THROW_HR_MSG(E_UNEXPECTED, "No active download found. Wait failed."); - } - - return m_downloadTask.get(); + THROW_HR_IF(E_INVALIDARG, url.empty()); + THROW_HR_IF(E_INVALIDARG, dest.empty()); + return Future<std::vector<BYTE>>(std::bind(DownloadAsyncInternal, std::placeholders::_1, url, dest, computeHash)); } - - std::vector<BYTE> Downloader::GetDownloadHash() - { - if (m_downloadHash.size() == 0) - { - THROW_HR_MSG(E_UNEXPECTED, "Invalid sha256 length. Download in progress or hash calculation not requested."); - } - - return m_downloadHash; - }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <AppInstallerFuture.h> +#include <winrt/Windows.Foundation.h> +#include <winrt/Windows.Management.Deployment.h> + +namespace AppInstaller::Deployment +{ + // Calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync as a Future. + Future<void> RequestAddPackageAsync( + const winrt::Windows::Foundation::Uri& uri, + winrt::Windows::Management::Deployment::DeploymentOptions options); +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h @@ -1,67 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once +#include <AppInstallerFuture.h> namespace AppInstaller::Utility { - // Enum used by Downloader to report download result - enum class DownloaderResult - { - Success = 0, - Failed, - Canceled - }; - - // Callback interface that can be passed in to downloader to get download updates. - class IDownloaderCallback - { - public: - virtual void OnStarted(LONGLONG totalBytes) = 0; - - virtual void OnProgress(LONGLONG bytesDownloaded, LONGLONG totalBytes) = 0; - - virtual void OnCanceled() = 0; - - virtual void OnCompleted() = 0; - }; - - // Downloader class to handle 1 file download per instance. The Downloader class supports - // SHA 256 calculation as downloading happens. - class Downloader - { - public: - // This is the only method to get a Downloader instance. - // url: The url to be downloaded from. http->https redirection is allowed. - // dest: The path to local file to be downloaded to. - // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. - // callback: Optional. Pass in an object implementing IDownloaderCallback to receive download updates. - static std::unique_ptr<Downloader> StartDownloadAsync( - const std::string& url, - const std::filesystem::path& dest, - bool computeHash = false, - IDownloaderCallback* callback = nullptr); - - // Cancel the download. - DownloaderResult Cancel(); - - // Wait for the download to finish. - DownloaderResult Wait(); - - // Get download content hash only if download is success and hash calculation is requested. - std::vector<BYTE> GetDownloadHash(); - - private: - std::shared_future<DownloaderResult> m_downloadTask; - std::atomic<bool> m_cancelled = false; - std::vector<BYTE> m_downloadHash; - - Downloader() {}; - - // The internal method which does actual downloading. - DownloaderResult DownloadInternal( - const std::string& url, - const std::filesystem::path& dest, - bool computeHash, - IDownloaderCallback* callback); - }; + // Downloads a file from the given URL and places it in the given location. + // url: The url to be downloaded from. http->https redirection is allowed. + // dest: The path to local file to be downloaded to. + // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. + Future<std::vector<BYTE>> DownloadAsync( + const std::string& url, + const std::filesystem::path& dest, + bool computeHash = false); } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/AppInstallerFuture.h b/src/AppInstallerCommonCore/Public/AppInstallerFuture.h @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <wil/resource.h> + +#include <atomic> +#include <future> +#include <optional> +#include <type_traits> +#include <utility> + +namespace AppInstaller +{ + // The semantic meaning of the progress values. + enum class FutureProgressType + { + // Progress will not be sent. + None, + Bytes, + Percent, + }; + + // Callback interface for receiving progress data. + struct IFutureProgress + { + // Called when the future has started processing. + virtual void OnStarted() = 0; + + // Called as progress is made. + // If maximum is 0, the maximum is unknown. + virtual void OnProgress(uint64_t current, uint64_t maximum, FutureProgressType type) = 0; + + // Called when the future is done processing. + virtual void OnCompleted(bool cancelled) = 0; + }; + + // Callback interface given to the promise keeper to work with. + struct IPromiseKeeperProgress + { + // Called as progress is made. + // If maximum is 0, the maximum is unknown. + virtual void OnProgress(uint64_t current, uint64_t maximum, FutureProgressType type) = 0; + + // Returns a value indicating if the future has been cancelled. + virtual bool IsCancelled() = 0; + }; + + namespace details + { + struct PromiseKeeperProgress : public IPromiseKeeperProgress + { + void OnStarted() + { + IFutureProgress* receiver = GetFutureProgress(); + if (receiver) + { + receiver->OnStarted(); + } + } + + void OnProgress(uint64_t current, uint64_t maximum, FutureProgressType type) override + { + IFutureProgress* receiver = GetFutureProgress(); + if (receiver) + { + receiver->OnProgress(current, maximum, type); + } + } + + void OnCompleted(bool cancelled) + { + IFutureProgress* receiver = GetFutureProgress(); + if (receiver) + { + receiver->OnCompleted(cancelled); + } + } + + bool IsCancelled() override + { + return m_cancelled.load(); + } + + IFutureProgress* GetFutureProgress() + { + return m_receiver.load(); + } + + std::atomic<IFutureProgress*> m_receiver = nullptr; + std::atomic_bool m_cancelled = false; + }; + + // Helper to determine template type info. + template <typename T> + struct TemplateDeduction + { + using t = T; + }; + + template <template<typename> typename T, typename U> + struct TemplateDeduction<T<U>> + { + using t = T<void>; + }; + + template <typename T> + using TemplateDeduction_t = typename TemplateDeduction<T>::t; + } + + // Future wrapper that enables progress to be hooked up by caller. + template <typename Result> + struct Future + { + using Task = std::packaged_task<Result(IPromiseKeeperProgress*)>; + // If the incoming Result type is void or std::optional, just use that as the return type for Get. + // Otherwise, wrap it in std::optional. + using GetResult = std::conditional_t<std::is_same_v<void, Result> || std::is_same_v<std::optional<void>, details::TemplateDeduction_t<Result>>, Result, std::optional<Result>>; + + Future(Task&& task) : m_task(std::move(task)) {} + + template <typename F> + explicit Future(F&& f) : m_task(std::move(f)) {} + + Future(const Future&) = delete; + Future& operator=(const Future&) = delete; + + Future(Future&&) = default; + Future& operator=(Future&&) = default; + + // Cancel the processing of the future, if possible. + void Cancel() { m_progress.m_cancelled = true; } + + // Gets the result, waiting as required. + // If cancelled, result depends on promise keeper. + GetResult Get() + { + m_progress.OnStarted(); + + std::future future = m_task.get_future(); + m_task(&m_progress); + + auto scopeExit = wil::scope_exit([&]() + { + if (m_progress.IsCancelled()) + { + m_progress.OnCompleted(true); + } + else + { + m_progress.OnCompleted(false); + } + }); + + future.wait(); + + if (m_progress.IsCancelled()) + { + return HandleDefaultReturn<GetResult>::Return(); + } + else + { + return future.get(); + } + } + + // Sets the progress receiver. + void SetProgressReceiver(IFutureProgress* receiver) { m_progress.m_receiver = receiver; } + + private: + template <typename T> + struct HandleDefaultReturn { static T Return() { return T{}; } }; + + template <> + struct HandleDefaultReturn<void> { static void Return() {} }; + + Task m_task; + details::PromiseKeeperProgress m_progress; + }; +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerLogging.h b/src/AppInstallerCommonCore/Public/AppInstallerLogging.h @@ -34,6 +34,7 @@ namespace AppInstaller::Logging SQL, Repo, YAML, + Core, Test, All, }; diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -33,8 +33,11 @@ #include <AppxPackaging.h> #include <chrono> +#include <cwctype> #include <filesystem> #include <fstream> +#include <functional> +#include <future> #include <iomanip> #include <limits> #include <memory> @@ -44,5 +47,3 @@ #include <string_view> #include <type_traits> #include <vector> -#include <future> -#include <cwctype>- \ No newline at end of file