winget-cli

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

commit db05660d55f4df442c8edbc3e869199078d20e3d
parent bb67e380f53e8150945f9043e560db2f44cdf4e4
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date:   Tue, 26 Oct 2021 22:18:38 -0700

Refactor Source Interface to be more object oriented (#1631)


Diffstat:
Msrc/AppInstallerCLICore/Commands/SourceCommand.cpp | 24++----------------------
Msrc/AppInstallerCLICore/ContextOrchestrator.cpp | 12++++++------
Msrc/AppInstallerCLICore/ContextOrchestrator.h | 3++-
Msrc/AppInstallerCLICore/ExecutionContextData.h | 9++++-----
Msrc/AppInstallerCLICore/PackageCollection.h | 2+-
Msrc/AppInstallerCLICore/Workflows/CompletionFlow.cpp | 2+-
Msrc/AppInstallerCLICore/Workflows/CompletionFlow.h | 2+-
Msrc/AppInstallerCLICore/Workflows/DependenciesFlow.cpp | 5+++--
Msrc/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp | 2+-
Msrc/AppInstallerCLICore/Workflows/DependencyNodeProcessor.h | 2+-
Msrc/AppInstallerCLICore/Workflows/DownloadFlow.cpp | 4++--
Msrc/AppInstallerCLICore/Workflows/ImportExportFlow.cpp | 17+++++++++--------
Msrc/AppInstallerCLICore/Workflows/InstallFlow.cpp | 18+++++++++++-------
Msrc/AppInstallerCLICore/Workflows/ManifestComparator.h | 2+-
Msrc/AppInstallerCLICore/Workflows/SourceFlow.cpp | 121++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/AppInstallerCLICore/Workflows/SourceFlow.h | 4++--
Msrc/AppInstallerCLICore/Workflows/UninstallFlow.cpp | 8++++----
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 164+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.h | 10+++-------
Msrc/AppInstallerCLICore/pch.h | 3+--
Msrc/AppInstallerCLIE2ETests/SearchCommand.cs | 2+-
Msrc/AppInstallerCLITests/CompositeSource.cpp | 6+++---
Msrc/AppInstallerCLITests/CustomHeader.cpp | 78------------------------------------------------------------------------------
Msrc/AppInstallerCLITests/Dependencies.cpp | 11+++++------
Msrc/AppInstallerCLITests/DependenciesTestSource.h | 7+++----
Msrc/AppInstallerCLITests/PackageTrackingCatalog.cpp | 17+++++++++--------
Msrc/AppInstallerCLITests/PreIndexedPackageSource.cpp | 3++-
Msrc/AppInstallerCLITests/PredefinedInstalledSource.cpp | 4++--
Msrc/AppInstallerCLITests/SQLiteIndexSource.cpp | 3++-
Msrc/AppInstallerCLITests/Sources.cpp | 40++++++++++++++++++++++++----------------
Msrc/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yaml | 1+
Msrc/AppInstallerCLITests/TestSource.cpp | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/AppInstallerCLITests/TestSource.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/AppInstallerCLITests/WorkFlow.cpp | 131++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj | 7++++---
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters | 23+++++++++++++----------
Msrc/AppInstallerRepositoryCore/CompositeSource.cpp | 18+++++++++++-------
Msrc/AppInstallerRepositoryCore/CompositeSource.h | 4+---
Asrc/AppInstallerRepositoryCore/ISource.h | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.cpp | 38+++++++++++++++++++++++++++++---------
Msrc/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.h | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp | 117++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.h | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp | 34+++++++++++++++++++++++++++-------
Msrc/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp | 97++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp | 18+++++++++++-------
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h | 17++++++++++++++---
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable.h | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp | 6+++---
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h | 2+-
Msrc/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h | 2+-
Msrc/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp | 11++++++-----
Dsrc/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h | 310-------------------------------------------------------------------------------
Dsrc/AppInstallerRepositoryCore/Public/AppInstallerRepositorySource.h | 260-------------------------------------------------------------------------------
Msrc/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h | 5++---
Asrc/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h | 313+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerRepositoryCore/Public/winget/RepositorySource.h | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerRepositoryCore/RepositorySearch.cpp | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/RepositorySource.cpp | 815++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/AppInstallerRepositoryCore/Rest/RestClient.h | 2+-
Msrc/AppInstallerRepositoryCore/Rest/RestSource.cpp | 45+++++++++++++++++++++------------------------
Msrc/AppInstallerRepositoryCore/Rest/RestSource.h | 19++++++++++++-------
Msrc/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp | 71++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/AppInstallerRepositoryCore/Rest/RestSourceFactory.h | 2+-
Msrc/AppInstallerRepositoryCore/SourceFactory.h | 4++--
Msrc/AppInstallerRepositoryCore/SourceList.cpp | 39+++++++++++++--------------------------
Msrc/AppInstallerRepositoryCore/SourceList.h | 11++++++++---
Msrc/Microsoft.Management.Deployment/CatalogPackage.cpp | 5++---
Msrc/Microsoft.Management.Deployment/CatalogPackage.h | 4++--
Msrc/Microsoft.Management.Deployment/Converters.cpp | 3+--
Msrc/Microsoft.Management.Deployment/Converters.h | 3+--
Msrc/Microsoft.Management.Deployment/MatchResult.cpp | 3+--
Msrc/Microsoft.Management.Deployment/PackageCatalog.cpp | 6+++---
Msrc/Microsoft.Management.Deployment/PackageCatalog.h | 4++--
Msrc/Microsoft.Management.Deployment/PackageCatalogInfo.cpp | 2+-
Msrc/Microsoft.Management.Deployment/PackageCatalogInfo.h | 4++--
Msrc/Microsoft.Management.Deployment/PackageCatalogReference.cpp | 56+++++++++++++++++++++++++-------------------------------
Msrc/Microsoft.Management.Deployment/PackageCatalogReference.h | 5++++-
Msrc/Microsoft.Management.Deployment/PackageManager.cpp | 43+++++++++++++++++++++++++------------------
Msrc/Microsoft.Management.Deployment/PackageManager.idl | 46+++++++++++++++++++++++-----------------------
Msrc/Microsoft.Management.Deployment/PackageMatchFilter.cpp | 3+--
Msrc/Microsoft.Management.Deployment/PackageVersionId.cpp | 2+-
Msrc/Microsoft.Management.Deployment/PackageVersionInfo.cpp | 4++--
86 files changed, 2122 insertions(+), 1752 deletions(-)

diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -80,28 +80,8 @@ namespace AppInstaller::CLI Workflow::EnsureRunningAsAdmin << Workflow::GetSourceList << Workflow::CheckSourceListAgainstAdd << - // TODO: Could improve the workflow by opening the source before adding during ISource refactoring work - Workflow::AddSource << - Workflow::OpenSourceForSourceAdd; - - if (context.IsTerminated()) - { - if (context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED || - context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED) - { - auto contextForRemovePtr = context.Clone(); - Context& contextForRemove = *contextForRemovePtr; - contextForRemove.Args.AddArg(Args::Type::SourceName, context.Args.GetArg(Args::Type::SourceName)); - - contextForRemove << - Workflow::GetSourceListWithFilter << - Workflow::RemoveSources; - } - } - else - { - context.Reporter.Info() << Resource::String::Done << std::endl; - } + Workflow::CreateSourceForSourceAdd << + Workflow::AddSource; } std::vector<Argument> SourceListCommand::GetArguments() const diff --git a/src/AppInstallerCLICore/ContextOrchestrator.cpp b/src/AppInstallerCLICore/ContextOrchestrator.cpp @@ -18,9 +18,9 @@ namespace AppInstaller::CLI::Execution ContextOrchestrator::ContextOrchestrator() { - ::AppInstaller::ProgressCallback progress; - std::shared_ptr<::AppInstaller::Repository::ISource> installingSource = ::AppInstaller::Repository::OpenPredefinedSource(::AppInstaller::Repository::PredefinedSource::Installing, progress); - m_installingWriteableSource = std::dynamic_pointer_cast<::AppInstaller::Repository::IMutablePackageSource>(installingSource); + ProgressCallback progress; + m_installingWriteableSource = Repository::Source(Repository::PredefinedSource::Installing); + m_installingWriteableSource.Open(progress); } _Requires_lock_held_(m_queueLock) @@ -49,9 +49,9 @@ namespace AppInstaller::CLI::Execution m_queueItems.push_back(item); } - // Add the package to the Installing source so that it can be queried using the ISource interface. + // Add the package to the Installing source so that it can be queried using the Source interface. const auto& manifest = item->GetContext().Get<Execution::Data::Manifest>(); - m_installingWriteableSource->AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + m_installingWriteableSource.AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); { std::lock_guard<std::mutex> lockQueue{ m_queueLock }; @@ -166,7 +166,7 @@ namespace AppInstaller::CLI::Execution if (foundItem) { const auto& manifest = item.GetContext().Get<Execution::Data::Manifest>(); - m_installingWriteableSource->RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + m_installingWriteableSource.RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); item.GetCompletedEvent().SetEvent(); } diff --git a/src/AppInstallerCLICore/ContextOrchestrator.h b/src/AppInstallerCLICore/ContextOrchestrator.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include <AppInstallerLogging.h> +#include <winget/RepositorySource.h> #include "ExecutionReporter.h" #include "ExecutionArgs.h" #include "ExecutionContextData.h" @@ -85,7 +86,7 @@ namespace AppInstaller::CLI::Execution _Requires_lock_held_(m_queueLock) std::shared_ptr<OrchestratorQueueItem> FindById(const OrchestratorQueueItemId& queueItemId); - std::shared_ptr<::AppInstaller::Repository::IMutablePackageSource> m_installingWriteableSource = nullptr; + Repository::Source m_installingWriteableSource; std::deque<std::shared_ptr<OrchestratorQueueItem>> m_queueItems; }; } diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include <AppInstallerRepositorySearch.h> -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include <winget/Manifest.h> #include "CompletionData.h" #include "PackageCollection.h" @@ -96,7 +95,7 @@ namespace AppInstaller::CLI::Execution template <> struct DataMapping<Data::Source> { - using value_t = std::shared_ptr<Repository::ISource>; + using value_t = Repository::Source; }; template <> @@ -210,7 +209,7 @@ namespace AppInstaller::CLI::Execution template <> struct DataMapping<Data::Sources> { - using value_t = std::vector<std::shared_ptr<Repository::ISource>>; + using value_t = std::vector<Repository::Source>; }; template <> @@ -229,7 +228,7 @@ namespace AppInstaller::CLI::Execution template <> struct DataMapping<Data::DependencySource> { - using value_t = std::shared_ptr<Repository::ISource>; + using value_t = Repository::Source; }; template <> diff --git a/src/AppInstallerCLICore/PackageCollection.h b/src/AppInstallerCLICore/PackageCollection.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once #include "AppInstallerDateTime.h" -#include "AppInstallerRepositorySource.h" +#include "winget/RepositorySource.h" #include <winget/Manifest.h> #include <json.h> diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -29,7 +29,7 @@ namespace AppInstaller::CLI::Workflow const std::string& word = context.Get<Data::CompletionData>().Word(); auto stream = context.Reporter.Completion(); - for (const auto& source : Repository::GetSources()) + for (const auto& source : Repository::Source::GetCurrentSources()) { if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(source.Name, word)) { diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.h b/src/AppInstallerCLICore/Workflows/CompletionFlow.h @@ -3,7 +3,7 @@ #pragma once #include "ExecutionContext.h" #include "WorkflowBase.h" -#include <AppInstallerRepositorySearch.h> +#include <winget/RepositorySearch.h> namespace AppInstaller::CLI::Workflow { diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -101,12 +101,13 @@ namespace AppInstaller::CLI::Workflow if (context.Contains(Execution::Data::PackageVersion)) { const auto& packageVersion = context.Get<Execution::Data::PackageVersion>(); - context.Add<Execution::Data::DependencySource>(std::const_pointer_cast<Repository::ISource>(packageVersion->GetSource())); + context.Add<Execution::Data::DependencySource>(packageVersion->GetSource()); context << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); } else - { // install from manifest requires --dependency-source to be set + { + // install from manifest requires --dependency-source to be set context << Workflow::OpenSource(true) << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -21,7 +21,7 @@ namespace AppInstaller::CLI::Workflow searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependencyNode.Id)); - const auto& matches = source->Search(searchRequest).Matches; + const auto& matches = source.Search(searchRequest).Matches; if (matches.empty()) { diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.h b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once #include "pch.h" -#include <AppInstallerRepositorySearch.h> +#include <winget/RepositorySearch.h> #include "ExecutionContext.h" #include "winget/ManifestCommon.h" diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -385,8 +385,8 @@ namespace AppInstaller::CLI::Workflow context.SetFlags(Execution::ContextFlag::InstallerHashMatched); if (context.Contains(Execution::Data::PackageVersion) && - context.Get<Execution::Data::PackageVersion>()->GetSource() != nullptr && - WI_IsFlagSet(context.Get<Execution::Data::PackageVersion>()->GetSource()->GetDetails().TrustLevel, SourceTrustLevel::Trusted)) + context.Get<Execution::Data::PackageVersion>()->GetSource() && + WI_IsFlagSet(context.Get<Execution::Data::PackageVersion>()->GetSource().GetDetails().TrustLevel, SourceTrustLevel::Trusted)) { context.SetFlags(Execution::ContextFlag::InstallerTrusted); } diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -6,7 +6,7 @@ #include "UpdateFlow.h" #include "PackageCollection.h" #include "WorkflowBase.h" -#include "AppInstallerRepositorySearch.h" +#include <winget/RepositorySearch.h> namespace AppInstaller::CLI::Workflow { @@ -24,9 +24,9 @@ namespace AppInstaller::CLI::Workflow return source.Details; } - SourceDetails GetSourceDetails(const std::shared_ptr<ISource>& source) + SourceDetails GetSourceDetails(const Repository::Source& source) { - return source->GetDetails(); + return source.GetDetails(); } // Creates a predicate that determines whether a source matches a description in a SourceDetails. @@ -115,7 +115,7 @@ namespace AppInstaller::CLI::Workflow continue; } - const auto& sourceDetails = availablePackageVersion->GetSource()->GetDetails(); + const auto& sourceDetails = availablePackageVersion->GetSource().GetDetails(); AICLI_LOG(CLI, Info, << "Installed package is available. Package Id [" << availablePackageVersion->GetProperty(PackageVersionProperty::Id) << "], Source [" << sourceDetails.Identifier << "]"); @@ -218,7 +218,7 @@ namespace AppInstaller::CLI::Workflow void OpenSourcesForImport(Execution::Context& context) { - auto availableSources = Repository::GetSources(); + auto availableSources = Repository::Source::GetCurrentSources(); for (auto& requiredSource : context.Get<Execution::Data::PackageCollection>().Sources) { // Find the installed source matching the one described in the collection. @@ -262,7 +262,7 @@ namespace AppInstaller::CLI::Workflow // Search for all the packages in the source. // Each search is done in a sub context to search everything regardless of previous failures. - auto source = Repository::CreateCompositeSource(context.Get<Execution::Data::Source>(), *sourceItr, CompositeSearchBehavior::AllPackages); + Repository::Source source{ context.Get<Execution::Data::Source>(), *sourceItr, CompositeSearchBehavior::AllPackages }; AICLI_LOG(CLI, Info, << "Searching for packages requested from source [" << requiredSource.Details.Identifier << "]"); for (const auto& packageRequest : requiredSource.Packages) { @@ -276,7 +276,7 @@ namespace AppInstaller::CLI::Workflow auto searchContextPtr = context.Clone(); Execution::Context& searchContext = *searchContextPtr; searchContext.Add<Execution::Data::Source>(source); - searchContext.Add<Execution::Data::SearchResult>(source->Search(searchRequest)); + searchContext.Add<Execution::Data::SearchResult>(source.Search(searchRequest)); // TODO: In the future, it would be better to not have to convert back and forth from a string searchContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(packageRequest.Scope)); @@ -356,4 +356,4 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; } } -} +}+ \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -481,15 +481,17 @@ namespace AppInstaller::CLI::Workflow if (installer && MightWriteToARP(installer->InstallerType)) { - std::shared_ptr<ISource> arpSource = context.Reporter.ExecuteWithProgress( + Source arpSource = context.Reporter.ExecuteWithProgress( [](IProgressCallback& progress) { - return Repository::OpenPredefinedSource(PredefinedSource::ARP, progress); + Repository::Source result = Repository::Source(PredefinedSource::ARP); + result.Open(progress); + return result; }, true); std::vector<std::tuple<Utility::LocIndString, Utility::LocIndString, Utility::LocIndString>> entries; - for (const auto& entry : arpSource->Search({}).Matches) + for (const auto& entry : arpSource.Search({}).Matches) { auto installed = entry.Package->GetInstalledVersion(); if (installed) @@ -515,15 +517,17 @@ namespace AppInstaller::CLI::Workflow const auto& entries = context.Get<Execution::Data::ARPSnapshot>(); // Open it again to get the (potentially) changed ARP entries - std::shared_ptr<ISource> arpSource = context.Reporter.ExecuteWithProgress( + Source arpSource = context.Reporter.ExecuteWithProgress( [](IProgressCallback& progress) { - return Repository::OpenPredefinedSource(PredefinedSource::ARP, progress); + Repository::Source result = Repository::Source(PredefinedSource::ARP); + result.Open(progress); + return result; }, true); std::vector<ResultMatch> changes; - for (auto& entry : arpSource->Search({}).Matches) + for (auto& entry : arpSource.Search({}).Matches) { auto installed = entry.Package->GetInstalledVersion(); @@ -588,7 +592,7 @@ namespace AppInstaller::CLI::Workflow // Don't execute this search if it would just find everything if (!nameAndPublisherRequest.IsForEverything()) { - findByManifest = arpSource->Search(nameAndPublisherRequest); + findByManifest = arpSource.Search(nameAndPublisherRequest); } // Cross reference the changes with the search results diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -3,7 +3,7 @@ #pragma once #include "ExecutionArgs.h" #include <winget/Manifest.h> -#include <AppInstallerRepositorySearch.h> +#include <winget/RepositorySearch.h> #include <memory> #include <string> diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp @@ -14,62 +14,62 @@ namespace AppInstaller::CLI::Workflow void GetSourceList(Execution::Context& context) { - context.Add<Execution::Data::SourceList>(Repository::GetSources()); + context.Add<Execution::Data::SourceList>(Repository::Source::GetCurrentSources()); } void GetSourceListWithFilter(Execution::Context& context) { + auto currentSources = Repository::Source::GetCurrentSources(); if (context.Args.Contains(Args::Type::SourceName)) { std::string_view name = context.Args.GetArg(Args::Type::SourceName); - std::optional<Repository::SourceDetails> source = Repository::GetSource(name); - if (!source) + for (auto const& source : currentSources) { - context.Reporter.Error() << Resource::String::SourceListNoneFound << ' ' << name << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); + if (Utility::ICUCaseInsensitiveEquals(source.Name, name)) + { + std::vector<Repository::SourceDetails> sources; + sources.emplace_back(source); + context.Add<Execution::Data::SourceList>(std::move(sources)); + return; + } } - std::vector<Repository::SourceDetails> sources; - sources.emplace_back(std::move(source.value())); - context.Add<Execution::Data::SourceList>(std::move(sources)); + context.Reporter.Error() << Resource::String::SourceListNoneFound << ' ' << name << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); } else { - context.Add<Execution::Data::SourceList>(Repository::GetSources()); + context.Add<Execution::Data::SourceList>(std::move(currentSources)); } } void CheckSourceListAgainstAdd(Execution::Context& context) { + auto sourceList = context.Get<Execution::Data::SourceList>(); std::string_view name = context.Args.GetArg(Args::Type::SourceName); std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); + std::string_view type = context.Args.GetArg(Args::Type::SourceType); - // First check if this is going to be a name conflict - std::optional<Repository::SourceDetails> source = Repository::GetSource(name); - if (source) + for (const auto& details : sourceList) { - if (source->Arg == arg) + if (Utility::ICUCaseInsensitiveEquals(details.Name, name)) { - // Name and arg match, indicate this to the user and bail. - context.Reporter.Info() << Resource::String::SourceAddAlreadyExistsMatch << std::endl << - " "_liv << source->Name << " -> "_liv << source->Arg << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); - } - else - { - context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentArg << std::endl << - " "_liv << source->Name << " -> "_liv << source->Arg << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); + if (details.Arg == arg) + { + // Name and arg match, indicate this to the user and bail. + context.Reporter.Info() << Resource::String::SourceAddAlreadyExistsMatch << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); + } + else + { + context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentArg << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); + } } - } - // Now check if the URL is already in use under a different name - auto sourceList = context.Get<Execution::Data::SourceList>(); - std::string_view type = context.Args.GetArg(Args::Type::SourceType); - - for (const auto& details : sourceList) - { if (!details.Arg.empty() && details.Arg == arg && details.Type == type) { context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentName << std::endl << @@ -81,41 +81,50 @@ namespace AppInstaller::CLI::Workflow void AddSource(Execution::Context& context) { - Repository::SourceDetails sourceDetails; - sourceDetails.Name = context.Args.GetArg(Args::Type::SourceName); - sourceDetails.Arg = context.Args.GetArg(Args::Type::SourceArg); - - if (context.Args.Contains(Args::Type::SourceType)) - { - sourceDetails.Type = context.Args.GetArg(Args::Type::SourceType); - } + auto& sourceToAdd = context.Get<Execution::Data::Source>(); + auto details = sourceToAdd.GetDetails(); context.Reporter.Info() << Resource::String::SourceAddBegin << std::endl << - " "_liv << sourceDetails.Name << " -> "_liv << sourceDetails.Arg << std::endl; + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; - if (!context.Reporter.ExecuteWithProgress(std::bind(Repository::AddSource, sourceDetails, std::placeholders::_1))) + auto addFunction = [&](IProgressCallback& progress)->bool { return sourceToAdd.Add(progress); }; + if (!context.Reporter.ExecuteWithProgress(addFunction)) { context.Reporter.Info() << Resource::String::Cancelled << std::endl; } + else + { + context.Reporter.Info() << Resource::String::Done << std::endl; + } } - void OpenSourceForSourceAdd(Execution::Context& context) + void CreateSourceForSourceAdd(Execution::Context& context) { try { - auto sourceDetails = Repository::GetSource(context.Args.GetArg(Args::Type::SourceName)); - sourceDetails.value().CustomHeader = GetCustomHeaderFromArg(context, sourceDetails.value()); + std::string_view name = context.Args.GetArg(Args::Type::SourceName); + std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); + std::string_view type = context.Args.GetArg(Args::Type::SourceType); - auto result = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenSourceFromDetails, sourceDetails.value(), std::placeholders::_1), true); + Repository::Source sourceToAdd{ name, arg, type }; - if (!result.Source) + if (context.Args.Contains(Execution::Args::Type::CustomHeader)) { - context.Reporter.Error() << Resource::String::SourceAddOpenSourceFailed; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED); + std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) }; + if (!sourceToAdd.SetCustomHeader(customHeader)) + { + context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; + } + } + + context << Workflow::HandleSourceAgreements(sourceToAdd); + if (context.IsTerminated()) + { + return; } - context << Workflow::HandleSourceAgreements(result.Source); + context.Add<Execution::Data::Source>(std::move(sourceToAdd)); } catch (...) { @@ -185,14 +194,16 @@ namespace AppInstaller::CLI::Workflow const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); for (const auto& sd : sources) { + Repository::Source source{ sd.Name }; context.Reporter.Info() << Resource::String::SourceUpdateOne << ' ' << sd.Name << "..."_liv << std::endl; - if (context.Reporter.ExecuteWithProgress(std::bind(Repository::UpdateSource, sd.Name, std::placeholders::_1))) + auto updateFunction = [&](IProgressCallback& progress)->std::vector<Repository::SourceDetails> { return source.Update(progress); }; + if (!context.Reporter.ExecuteWithProgress(updateFunction).empty()) { - context.Reporter.Info() << Resource::String::Done << std::endl; + context.Reporter.Info() << Resource::String::Cancelled << std::endl; } else { - context.Reporter.Info() << Resource::String::Cancelled << std::endl; + context.Reporter.Info() << Resource::String::Done << std::endl; } } } @@ -209,8 +220,10 @@ namespace AppInstaller::CLI::Workflow const std::vector<Repository::SourceDetails>& sources = context.Get<Data::SourceList>(); for (const auto& sd : sources) { + Repository::Source source{ sd.Name }; context.Reporter.Info() << Resource::String::SourceRemoveOne << ' ' << sd.Name << "..."_liv << std::endl; - if (context.Reporter.ExecuteWithProgress(std::bind(Repository::RemoveSource, sd.Name, std::placeholders::_1))) + auto removeFunction = [&](IProgressCallback& progress)->bool { return source.Remove(progress); }; + if (context.Reporter.ExecuteWithProgress(removeFunction)) { context.Reporter.Info() << Resource::String::Done << std::endl; } @@ -245,7 +258,7 @@ namespace AppInstaller::CLI::Workflow for (const auto& source : sources) { context.Reporter.Info() << Resource::String::SourceResetOne << ' ' << source.Name << "..."_liv; - Repository::DropSource(source.Name); + Repository::Source::DropSource(source.Name); context.Reporter.Info() << Resource::String::Done << std::endl; } } @@ -253,7 +266,7 @@ namespace AppInstaller::CLI::Workflow void ResetAllSources(Execution::Context& context) { context.Reporter.Info() << Resource::String::SourceResetAll; - Repository::DropSource({}); + Repository::Source::DropSource({}); context.Reporter.Info() << Resource::String::Done << std::endl; } diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.h b/src/AppInstallerCLICore/Workflows/SourceFlow.h @@ -31,10 +31,10 @@ namespace AppInstaller::CLI::Workflow void AddSource(Execution::Context& context); // Opens a source before source add command. - // Required Args: None + // Required Args: SourceName, SourceArg // Inputs: None // Outputs: Source - void OpenSourceForSourceAdd(Execution::Context& context); + void CreateSourceForSourceAdd(Execution::Context& context); // Lists the sources in SourceList. // Required Args: None diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -24,15 +24,15 @@ namespace AppInstaller::CLI::Workflow struct Item { Utility::LocIndString Identifier; - std::shared_ptr<const ISource> Source; + Source FromSource; std::string SourceIdentifier; }; void AddIfRemoteAndNotPresent(const std::shared_ptr<IPackageVersion>& packageVersion) { auto source = packageVersion->GetSource(); - const auto& details = source->GetDetails(); - if (!ContainsAvailablePackages(details.Origin)) + const auto details = source.GetDetails(); + if (!source.ContainsAvailablePackages()) { return; } @@ -181,7 +181,7 @@ namespace AppInstaller::CLI::Workflow // Finally record the uninstall for each found value for (const auto& item : correlatedSources.Items) { - auto trackingCatalog = PackageTrackingCatalog::CreateForSource(item.Source); + auto trackingCatalog = PackageTrackingCatalog::CreateForSource(item.FromSource); trackingCatalog.RecordUninstall(item.Identifier); } } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -20,7 +20,7 @@ namespace AppInstaller::CLI::Workflow { if (match.MatchCriteria.Field != PackageMatchField::Id && match.MatchCriteria.Field != PackageMatchField::Name) { - std::string result{ PackageMatchFieldToString(match.MatchCriteria.Field) }; + std::string result{ ToString(match.MatchCriteria.Field) }; result += ": "; result += match.MatchCriteria.Value; return result; @@ -41,32 +41,52 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << Resource::String::ReportIdentityFound << ' ' << Execution::NameEmphasis << name << " [" << Execution::IdEmphasis << id << "] " << Resource::String::ShowVersion << ' ' << version << std::endl; } - std::shared_ptr<ISource> OpenNamedSource(Execution::Context& context, std::string_view sourceName) + Repository::Source OpenNamedSource(Execution::Context& context, std::string_view sourceName) { - std::shared_ptr<Repository::ISource> source; + Repository::Source source; try { - OpenSourceResult result; - if (!sourceName.empty()) + source = Source{ sourceName }; + + if (!source) { - auto sourceDetails = Repository::GetSource(sourceName); - if (sourceDetails) + std::vector<SourceDetails> sources = Source::GetCurrentSources(); + + if (!sourceName.empty() && !sources.empty()) { - sourceDetails.value().CustomHeader = GetCustomHeaderFromArg(context, sourceDetails.value()); + // A bad name was given, try to help. + context.Reporter.Error() << Resource::String::OpenSourceFailedNoMatch << ' ' << sourceName << std::endl; + context.Reporter.Info() << Resource::String::OpenSourceFailedNoMatchHelp << std::endl; + for (const auto& details : sources) + { + context.Reporter.Info() << " "_liv << details.Name << std::endl; + } - result = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenSourceFromDetails, sourceDetails.value(), std::placeholders::_1), true); + AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, {}); + } + else + { + // Even if a name was given, there are no sources + context.Reporter.Error() << Resource::String::OpenSourceFailedNoSourceDefined << std::endl; + AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED, {}); } } - else + + if (context.Args.Contains(Execution::Args::Type::CustomHeader)) { - result = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenSource, sourceName, std::placeholders::_1), true); + std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) }; + if (!source.SetCustomHeader(customHeader)) + { + context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; + } } - source = result.Source; + auto openFunction = [&](IProgressCallback& progress)->std::vector<Repository::SourceDetails> { return source.Open(progress); }; + auto updateFailures = context.Reporter.ExecuteWithProgress(openFunction, true); // We'll only report the source update failure as warning and continue - for (const auto& s : result.SourcesWithUpdateFailure) + for (const auto& s : updateFailures) { context.Reporter.Warn() << Resource::String::SourceOpenWithFailedUpdate << ' ' << s.Name << std::endl; } @@ -91,30 +111,6 @@ namespace AppInstaller::CLI::Workflow throw; } - if (!source) - { - std::vector<SourceDetails> sources = GetSources(); - - if (!sourceName.empty() && !sources.empty()) - { - // A bad name was given, try to help. - context.Reporter.Error() << Resource::String::OpenSourceFailedNoMatch << ' ' << sourceName << std::endl; - context.Reporter.Info() << Resource::String::OpenSourceFailedNoMatchHelp << std::endl; - for (const auto& details : sources) - { - context.Reporter.Info() << " "_liv << details.Name << std::endl; - } - - AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, {}); - } - else - { - // Even if a name was given, there are no sources - context.Reporter.Error() << Resource::String::OpenSourceFailedNoSourceDefined << std::endl; - AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED, {}); - } - } - return source; } @@ -153,22 +149,23 @@ namespace AppInstaller::CLI::Workflow } } - bool HandleSourceAgreementsForOneSource(Execution::Context& context, const SourceDetails& source) + bool HandleSourceAgreementsForOneSource(Execution::Context& context, const Source& source) { - AICLI_LOG(CLI, Verbose, << "Checking Source agreements for source: " << source.Name); + auto details = source.GetDetails(); + AICLI_LOG(CLI, Verbose, << "Checking Source agreements for source: " << details.Name); - if (CheckSourceAgreements(source)) + if (source.CheckSourceAgreements()) { - AICLI_LOG(CLI, Verbose, << "Source agreements satisfied. Source: " << source.Name); + AICLI_LOG(CLI, Verbose, << "Source agreements satisfied. Source: " << details.Name); return true; } // Show source agreements std::string agreementsTitleMessage = Resource::LocString{ Resource::String::SourceAgreementsTitle }; context.Reporter.Info() << Execution::SourceInfoEmphasis << - Utility::LocIndString{ Utility::FindAndReplaceMessageToken(agreementsTitleMessage, source.Name) } << std::endl; + Utility::LocIndString{ Utility::FindAndReplaceMessageToken(agreementsTitleMessage, details.Name) } << std::endl; - const auto& agreements = source.Information.SourceAgreements; + const auto& agreements = source.GetInformation().SourceAgreements; for (const auto& agreement : agreements) { @@ -189,7 +186,7 @@ namespace AppInstaller::CLI::Workflow } // Show message for each individual implicit agreement field - auto fields = GetAgreementFieldsFromSourceInformation(source.Information); + auto fields = source.GetAgreementFieldsFromSourceInformation(); if (WI_IsFlagSet(fields, ImplicitAgreementFieldEnum::Market)) { context.Reporter.Info() << Resource::String::SourceAgreementsMarketMessage << std::endl; @@ -206,12 +203,12 @@ namespace AppInstaller::CLI::Workflow if (accepted) { - AICLI_LOG(CLI, Verbose, << "Source agreements accepted. Source: " << source.Name); - SaveAcceptedSourceAgreements(source); + AICLI_LOG(CLI, Verbose, << "Source agreements accepted. Source: " << details.Name); + source.SaveAcceptedSourceAgreements(); } else { - AICLI_LOG(CLI, Verbose, << "Source agreements rejected. Source: " << source.Name); + AICLI_LOG(CLI, Verbose, << "Source agreements rejected. Source: " << details.Name); } return accepted; @@ -364,10 +361,16 @@ namespace AppInstaller::CLI::Workflow void OpenPredefinedSource::operator()(Execution::Context& context) const { - std::shared_ptr<Repository::ISource> source; + Repository::Source source; try { - source = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, m_predefinedSource, std::placeholders::_1), true); + source = Source{ m_predefinedSource }; + + // A well known predefined source should return a value. + THROW_HR_IF(E_UNEXPECTED, !source); + + auto openFunction = [&](IProgressCallback& progress)->std::vector<Repository::SourceDetails> { return source.Open(progress); }; + context.Reporter.ExecuteWithProgress(openFunction, true); } catch (...) { @@ -375,9 +378,6 @@ namespace AppInstaller::CLI::Workflow throw; } - // A well known predefined source should return a value. - THROW_HR_IF(E_UNEXPECTED, !source); - if (m_forDependencies) { context.Add<Execution::Data::DependencySource>(std::move(source)); @@ -391,7 +391,7 @@ namespace AppInstaller::CLI::Workflow void OpenCompositeSource::operator()(Execution::Context& context) const { // Get the already open source for use as the available. - std::shared_ptr<Repository::ISource> availableSource; + Repository::Source availableSource; if (m_forDependencies) { availableSource = context.Get<Execution::Data::DependencySource>(); @@ -405,17 +405,17 @@ namespace AppInstaller::CLI::Workflow context << OpenPredefinedSource(m_predefinedSource, m_forDependencies); // Create the composite source from the two. - std::shared_ptr<Repository::ISource> source; - std::shared_ptr<Repository::ISource> compositeSource; + Repository::Source source; + Repository::Source compositeSource; if (m_forDependencies) { source = context.Get<Execution::Data::DependencySource>(); - compositeSource = Repository::CreateCompositeSource(source, availableSource, CompositeSearchBehavior::AvailablePackages); + compositeSource = Repository::Source{ source, availableSource, CompositeSearchBehavior::AvailablePackages }; } else { source = context.Get<Execution::Data::Source>(); - compositeSource = Repository::CreateCompositeSource(source, availableSource); + compositeSource = Repository::Source{ source, availableSource }; } // Overwrite the source with the composite. @@ -458,7 +458,7 @@ namespace AppInstaller::CLI::Workflow searchRequest.MaximumResults, searchRequest.ToString()); - context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>()->Search(searchRequest)); + context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest)); } void SearchSourceForSingle(Execution::Context& context) @@ -498,7 +498,7 @@ namespace AppInstaller::CLI::Workflow searchRequest.MaximumResults, searchRequest.ToString()); - context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>()->Search(searchRequest)); + context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest)); } void SearchSourceForManyCompletion(Execution::Context& context) @@ -511,7 +511,7 @@ namespace AppInstaller::CLI::Workflow SearchSourceApplyFilters(context, searchRequest, matchType); - context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>()->Search(searchRequest)); + context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest)); } void SearchSourceForSingleCompletion(Execution::Context& context) @@ -526,7 +526,7 @@ namespace AppInstaller::CLI::Workflow SearchSourceApplyFilters(context, searchRequest, matchType); - context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>()->Search(searchRequest)); + context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest)); } void SearchSourceForCompletionField::operator()(Execution::Context& context) const @@ -539,7 +539,7 @@ namespace AppInstaller::CLI::Workflow // If filters are provided, be generous with the search no matter the intended result. SearchSourceApplyFilters(context, searchRequest, MatchType::Substring); - context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>()->Search(searchRequest)); + context.Add<Execution::Data::SearchResult>(context.Get<Execution::Data::Source>().Search(searchRequest)); } void ReportSearchResult(Execution::Context& context) @@ -547,7 +547,7 @@ namespace AppInstaller::CLI::Workflow auto& searchResult = context.Get<Execution::Data::SearchResult>(); Logging::Telemetry().LogSearchResultCount(searchResult.Matches.size()); - bool sourceIsComposite = context.Get<Execution::Data::Source>()->IsComposite(); + bool sourceIsComposite = context.Get<Execution::Data::Source>().IsComposite(); Execution::TableOutput<5> table(context.Reporter, { Resource::String::SearchName, @@ -589,7 +589,7 @@ namespace AppInstaller::CLI::Workflow auto warn = context.Reporter.Warn(); for (const auto& failure : searchResult.Failures) { - warn << Resource::String::SearchFailureWarning << ' ' << failure.Source->GetDetails().Name << std::endl; + warn << Resource::String::SearchFailureWarning << ' ' << failure.SourceName << std::endl; } } else @@ -598,7 +598,7 @@ namespace AppInstaller::CLI::Workflow auto error = context.Reporter.Error(); for (const auto& failure : searchResult.Failures) { - error << Resource::String::SearchFailureError << ' ' << failure.Source->GetDetails().Name << std::endl; + error << Resource::String::SearchFailureError << ' ' << failure.SourceName << std::endl; HRESULT failureHR = HandleException(context, failure.Exception); // Just take first failure for now @@ -676,7 +676,7 @@ namespace AppInstaller::CLI::Workflow auto source = latest->GetSource(); if (source) { - sourceName = source->GetDetails().Name; + sourceName = source.GetDetails().Name; } } @@ -710,7 +710,7 @@ namespace AppInstaller::CLI::Workflow int availableUpgradesCount = 0; auto &source = context.Get<Execution::Data::Source>(); - bool shouldShowSource = source->IsComposite() && source->GetAvailableSources().size() > 1; + bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; for (const auto& match : searchResult.Matches) { @@ -980,24 +980,6 @@ namespace AppInstaller::CLI::Workflow } } - std::optional<std::string> GetCustomHeaderFromArg(Execution::Context& context, const SourceDetails& sourceDetails) - { - std::optional<std::string> customHeader; - if (context.Args.Contains(Execution::Args::Type::CustomHeader)) - { - if (!SupportsCustomHeader(sourceDetails)) - { - context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; - } - else - { - customHeader = context.Args.GetArg(Execution::Args::Type::CustomHeader); - } - } - - return customHeader; - } - void EnsureFeatureEnabled::operator()(Execution::Context& context) const { if (!Settings::ExperimentalFeature::IsEnabled(m_feature)) @@ -1029,7 +1011,7 @@ namespace AppInstaller::CLI::Workflow if (!searchRequest.Inclusions.empty()) { - auto searchResult = source->Search(searchRequest); + auto searchResult = source.Search(searchRequest); if (!searchResult.Matches.empty()) { @@ -1045,7 +1027,7 @@ namespace AppInstaller::CLI::Workflow // In case there're same Ids from different sources, filter the result using package name searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::CaseInsensitive, manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>())); - context.Add<Execution::Data::SearchResult>(source->Search(searchRequest)); + context.Add<Execution::Data::SearchResult>(source.Search(searchRequest)); } void GetInstalledPackageVersion(Execution::Context& context) @@ -1068,11 +1050,11 @@ namespace AppInstaller::CLI::Workflow bool allAccepted = true; - if (m_source->IsComposite()) + if (m_source.IsComposite()) { - for (auto const& source : m_source->GetAvailableSources()) + for (auto const& source : m_source.GetAvailableSources()) { - if (!HandleSourceAgreementsForOneSource(context, source->GetDetails())) + if (!HandleSourceAgreementsForOneSource(context, source)) { allAccepted = false; } @@ -1080,7 +1062,7 @@ namespace AppInstaller::CLI::Workflow } else { - allAccepted = HandleSourceAgreementsForOneSource(context, m_source->GetDetails()); + allAccepted = HandleSourceAgreementsForOneSource(context, m_source); } if (!allAccepted) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -3,7 +3,7 @@ #pragma once #include "ExecutionArgs.h" #include <winget/ExperimentalFeature.h> -#include <AppInstallerRepositorySearch.h> +#include <winget/RepositorySearch.h> #include <string> #include <string_view> @@ -320,10 +320,6 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void EnsureRunningAsAdmin(Execution::Context& context); - // Gets the custom header from Arguments. - // Returns: Custom header if provided and applicable. - std::optional<std::string> GetCustomHeaderFromArg(Execution::Context& context, const AppInstaller::Repository::SourceDetails& sourceDetails); - // Ensures that the feature is enabled. // Required Args: the desired feature // Inputs: None @@ -371,12 +367,12 @@ namespace AppInstaller::CLI::Workflow // Outputs: None struct HandleSourceAgreements : public WorkflowTask { - HandleSourceAgreements(std::shared_ptr<Repository::ISource> source) : WorkflowTask("HandleSourceAgreements"), m_source(std::move(source)) {} + HandleSourceAgreements(Repository::Source source) : WorkflowTask("HandleSourceAgreements"), m_source(std::move(source)) {} void operator()(Execution::Context& context) const override; private: - std::shared_ptr<Repository::ISource> m_source; + Repository::Source m_source; }; } diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h @@ -53,8 +53,7 @@ #include <AppInstallerDownloader.h> #include <AppInstallerErrors.h> #include <AppInstallerLogging.h> -#include <AppInstallerRepositorySearch.h> -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include <AppInstallerRuntime.h> #include <AppInstallerSHA256.h> #include <AppInstallerStrings.h> diff --git a/src/AppInstallerCLIE2ETests/SearchCommand.cs b/src/AppInstallerCLIE2ETests/SearchCommand.cs @@ -92,7 +92,7 @@ namespace AppInstallerCLIE2ETests [Test] public void SearchWithSingleSourceFailure() { - TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"CreateHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); + TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"OpenHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); var result = TestCommon.RunAICLICommand("search", "--exact AppInstallerTest.TestExampleInstaller"); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp @@ -677,7 +677,7 @@ TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]") REQUIRE(pfns[0] == pfn); REQUIRE(result.Failures.size() == 1); - REQUIRE(result.Failures[0].Source->GetDetails().Name == AvailableFails->Details.Name); + REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); HRESULT searchFailure = S_OK; try @@ -713,7 +713,7 @@ TEST_CASE("CompositeSource_InstalledToAvailableCorrelationSearchFailure", "[Comp REQUIRE(result.Matches.size() == 1); REQUIRE(result.Failures.size() == 1); - REQUIRE(result.Failures[0].Source->GetDetails().Name == AvailableFails->Details.Name); + REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); HRESULT searchFailure = S_OK; try @@ -757,7 +757,7 @@ TEST_CASE("CompositeSource_InstalledAvailableSearchFailure", "[CompositeSource]" REQUIRE(result.Matches.size() == 1); REQUIRE(result.Failures.size() == 1); - REQUIRE(result.Failures[0].Source->GetDetails().Name == AvailableFails->Details.Name); + REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); HRESULT searchFailure = S_OK; try diff --git a/src/AppInstallerCLITests/CustomHeader.cpp b/src/AppInstallerCLITests/CustomHeader.cpp @@ -85,84 +85,6 @@ TEST_CASE("RestClient_CustomHeader", "[RestSource][CustomHeader]") REQUIRE(client.GetSourceIdentifier() == "Source123"); } -TEST_CASE("AddSource_CustomHeader", "[RestSource][CustomHeader]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - std::string customHeader = "Testing custom header with open source"; - - SourceDetails details; - details.Name = "restsource"; - details.Type = "thisIsTheType"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - details.CustomHeader = customHeader; - - bool receivedCustomHeader = false; - TestSourceFactory factory{ [&](const SourceDetails& sd) { return std::shared_ptr<ISource>(new TestSource(sd)); } }; - factory.OnAdd = [&](SourceDetails& sd) { receivedCustomHeader = customHeader.compare(sd.CustomHeader.value()) == 0; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - TestProgress progress; - AddSource(details, progress); - REQUIRE(receivedCustomHeader); -} - -TEST_CASE("CreateSource_CustomHeader", "[RestSource][CustomHeader]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - std::string customHeader = "Testing custom header with open source"; - - SourceDetails details; - details.Name = "restsource"; - details.Type = "Microsoft.Rest"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - details.CustomHeader = customHeader; - - bool receivedCustomHeader = false; - TestSourceFactory factory{ [&](const SourceDetails& sd) { return std::shared_ptr<ISource>(new TestSource(sd)); } }; - factory.OnAdd = [&](SourceDetails& sd) { receivedCustomHeader = customHeader.compare(sd.CustomHeader.value()) == 0; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - TestProgress progress; - AddSource(details, progress); - - details.CustomHeader = "Custom Header"; - auto source = OpenSourceFromDetails(details, progress).Source; - REQUIRE(details.CustomHeader.value().compare(source.get()->GetDetails().CustomHeader.value_or("")) == 0); -} - -TEST_CASE("CreateSource_CustomHeaderNotApplicable", "[RestSource][CustomHeader]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - std::string customHeader = "Testing custom header with open source"; - - SourceDetails details; - details.Name = "restsource"; - details.Type = "thisIsTheType"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - details.CustomHeader = customHeader; - - bool receivedCustomHeader = false; - TestSourceFactory factory{ [&](const SourceDetails& sd) { return std::shared_ptr<ISource>(new TestSource(sd)); } }; - factory.OnAdd = [&](SourceDetails& sd) { receivedCustomHeader = customHeader.compare(sd.CustomHeader.value()) == 0; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - TestProgress progress; - AddSource(details, progress); - - details.CustomHeader = {}; - auto source = OpenSourceFromDetails(details, progress).Source; - REQUIRE(!source.get()->GetDetails().CustomHeader.has_value()); -} - TEST_CASE("RestSourceSearch_CustomHeader", "[RestSource][CustomHeader]") { utility::string_t customHeader = L"Testing custom header"; diff --git a/src/AppInstallerCLITests/Dependencies.cpp b/src/AppInstallerCLITests/Dependencies.cpp @@ -8,8 +8,7 @@ #include <AppInstallerStrings.h> #include <Workflows/DependenciesFlow.h> #include <Workflows/WorkflowBase.h> -#include <Public/AppInstallerRepositorySource.h> -#include <Public/AppInstallerRepositorySearch.h> +#include <winget/RepositorySource.h> #include <winget/ManifestYamlParser.h> #include <Resources.h> @@ -138,7 +137,7 @@ TEST_CASE("DependencyNodeProcessor_SkipInstalled", "[dependencies]") Manifest manifest = CreateFakeManifestWithDependencies("installed1"); - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); DependencyNodeProcessor nodeProcessor(context); Dependency rootAsDependency(DependencyType::Package, manifest.Id); @@ -156,7 +155,7 @@ TEST_CASE("DependencyNodeProcessor_NoInstallers", "[dependencies]") Manifest manifest = CreateFakeManifestWithDependencies("withoutInstallers"); - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); DependencyNodeProcessor nodeProcessor(context); Dependency rootAsDependency(DependencyType::Package, manifest.Id); @@ -175,7 +174,7 @@ TEST_CASE("DependencyNodeProcessor_StackOrderIsOk", "[dependencies]") Manifest manifest = CreateFakeManifestWithDependencies("StackOrderIsOk"); - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); DependencyNodeProcessor nodeProcessor(context); Dependency rootAsDependency(DependencyType::Package, manifest.Id); @@ -196,7 +195,7 @@ TEST_CASE("DependencyNodeProcessor_NoMatches", "[dependencies]") Manifest manifest = CreateFakeManifestWithDependencies("NoMatches"); - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); DependencyNodeProcessor nodeProcessor(context); Dependency rootAsDependency(DependencyType::Package, manifest.Id); diff --git a/src/AppInstallerCLITests/DependenciesTestSource.h b/src/AppInstallerCLITests/DependenciesTestSource.h @@ -3,8 +3,7 @@ #include "pch.h" #include "TestSource.h" #include "TestCommon.h" -#include <Public/AppInstallerRepositorySearch.h> -#include <Public/AppInstallerRepositorySource.h> +#include <Public/winget/RepositorySource.h> #include <winget/ManifestYamlParser.h> using namespace AppInstaller::Repository; @@ -181,7 +180,7 @@ namespace TestCommon manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest }, - const_cast<DependenciesTestSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); } @@ -191,7 +190,7 @@ namespace TestCommon ResultMatch( TestPackage::Make( std::vector<Manifest>{ manifest }, - const_cast<DependenciesTestSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); } diff --git a/src/AppInstallerCLITests/PackageTrackingCatalog.cpp b/src/AppInstallerCLITests/PackageTrackingCatalog.cpp @@ -14,7 +14,7 @@ using namespace AppInstaller::Repository::Microsoft; using namespace AppInstaller::Repository::SQLite; using namespace AppInstaller::Utility; -static std::shared_ptr<SQLiteIndexSource> SimpleTestSetup(const std::string& filePath, SourceDetails& details, Manifest& manifest, std::string& relativePath) +static Source SimpleTestSetup(const std::string& filePath, SourceDetails& details, Manifest& manifest, std::string& relativePath) { SQLiteIndex index = SQLiteIndex::CreateNew(filePath, Schema::Version::Latest()); @@ -25,16 +25,17 @@ static std::shared_ptr<SQLiteIndexSource> SimpleTestSetup(const std::string& fil index.AddManifest(manifest, relativePath); + details.Identifier = "*SimpleTestSetup"; details.Name = "TestName"; details.Type = "TestType"; details.Arg = testManifest.GetPath().parent_path().u8string(); details.Data = ""; - auto result = std::make_shared<SQLiteIndexSource>(details, "*SimpleTestSetup", std::move(index)); + auto result = std::make_shared<SQLiteIndexSource>(details, std::move(index)); PackageTrackingCatalog::RemoveForSource(result->GetIdentifier()); - return result; + return { result }; } TEST_CASE("TrackingCatalog_Create", "[tracking_catalog]") @@ -45,7 +46,7 @@ TEST_CASE("TrackingCatalog_Create", "[tracking_catalog]") SourceDetails details; Manifest manifest; std::string relativePath; - std::shared_ptr<SQLiteIndexSource> source = SimpleTestSetup(tempFile, details, manifest, relativePath); + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); PackageTrackingCatalog catalog = PackageTrackingCatalog::CreateForSource(source); } @@ -58,7 +59,7 @@ TEST_CASE("TrackingCatalog_Install", "[tracking_catalog]") SourceDetails details; Manifest manifest; std::string relativePath; - std::shared_ptr<SQLiteIndexSource> source = SimpleTestSetup(tempFile, details, manifest, relativePath); + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); PackageTrackingCatalog catalog = PackageTrackingCatalog::CreateForSource(source); @@ -88,7 +89,7 @@ TEST_CASE("TrackingCatalog_Reinstall", "[tracking_catalog]") SourceDetails details; Manifest manifest; std::string relativePath; - std::shared_ptr<SQLiteIndexSource> source = SimpleTestSetup(tempFile, details, manifest, relativePath); + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); PackageTrackingCatalog catalog = PackageTrackingCatalog::CreateForSource(source); @@ -122,7 +123,7 @@ TEST_CASE("TrackingCatalog_Upgrade", "[tracking_catalog]") SourceDetails details; Manifest manifest; std::string relativePath; - std::shared_ptr<SQLiteIndexSource> source = SimpleTestSetup(tempFile, details, manifest, relativePath); + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); PackageTrackingCatalog catalog = PackageTrackingCatalog::CreateForSource(source); @@ -155,7 +156,7 @@ TEST_CASE("TrackingCatalog_Uninstall", "[tracking_catalog]") SourceDetails details; Manifest manifest; std::string relativePath; - std::shared_ptr<SQLiteIndexSource> source = SimpleTestSetup(tempFile, details, manifest, relativePath); + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); PackageTrackingCatalog catalog = PackageTrackingCatalog::CreateForSource(source); diff --git a/src/AppInstallerCLITests/PreIndexedPackageSource.cpp b/src/AppInstallerCLITests/PreIndexedPackageSource.cpp @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" +#include "TestSource.h" #include "TestCommon.h" #include "TestSettings.h" -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include <AppInstallerRuntime.h> #include <AppInstallerStrings.h> #include <Microsoft/PreIndexedPackageSourceFactory.h> diff --git a/src/AppInstallerCLITests/PredefinedInstalledSource.cpp b/src/AppInstallerCLITests/PredefinedInstalledSource.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "TestCommon.h" -#include <AppInstallerRepositorySource.h> +#include <ISource.h> #include <AppInstallerRuntime.h> #include <AppInstallerStrings.h> #include <Microsoft/PredefinedInstalledSourceFactory.h> @@ -142,7 +142,7 @@ std::shared_ptr<ISource> CreatePredefinedInstalledSource(Factory::Filter filter TestProgress progress; auto factory = Factory::Create(); - return factory->Create(details, progress); + return factory->Create(details)->Open(progress); } TEST_CASE("ARPHelper_GetARPForArchitecture", "[arphelper][list]") diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -27,8 +27,9 @@ static std::shared_ptr<SQLiteIndexSource> SimpleTestSetup(const std::string& fil details.Type = "TestType"; details.Arg = testManifest.GetPath().parent_path().u8string(); details.Data = ""; + details.Identifier = "*SimpleTestSetup"; - return std::make_shared<SQLiteIndexSource>(details, "*SimpleTestSetup", std::move(index)); + return std::make_shared<SQLiteIndexSource>(details, std::move(index)); } TEST_CASE("SQLiteIndexSource_Search_IdExactMatch", "[sqliteindexsource]") diff --git a/src/AppInstallerCLITests/Sources.cpp b/src/AppInstallerCLITests/Sources.cpp @@ -6,7 +6,6 @@ #include "TestSettings.h" #include "TestSource.h" -#include <AppInstallerRepositorySource.h> #include <AppInstallerDateTime.h> #include <AppInstallerRuntime.h> #include <AppInstallerStrings.h> @@ -581,7 +580,7 @@ TEST_CASE("RepoSources_UpdateOnOpen", "[sources]") SetSetting(Stream::UserSources, s_SingleSource); ProgressCallback progress; - auto source = OpenSource(name, progress).Source; + auto source = OpenSource(name, progress); REQUIRE(updateCalledOnFactory); @@ -645,10 +644,10 @@ TEST_CASE("RepoSources_SearchAcrossMultipleSources", "[sources]") SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest); ProgressCallback progress; - auto source = OpenSource("", progress).Source; + auto source = OpenSource("", progress); SearchRequest request; - auto result = source->Search(request); + auto result = source.Search(request); REQUIRE(result.Matches.size() == 6); REQUIRE_FALSE(result.Truncated); // matches are sorted in expected order @@ -661,7 +660,7 @@ TEST_CASE("RepoSources_SearchAcrossMultipleSources", "[sources]") // when truncate required request.MaximumResults = 3; - result = source->Search(request); + result = source.Search(request); REQUIRE(result.Matches.size() == 3); REQUIRE(result.Truncated); // matches are sorted in expected order @@ -875,6 +874,11 @@ TEST_CASE("RepoSources_GroupPolicy_AdditionalSources", "[sources][groupPolicy]") policySource.Data = "data"; policySource.Identifier = "id"; + bool removeCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(policySource.Type, factory); + policies.SetValue<ValuePolicy::AdditionalSources>({ policySource }); SetSetting(Stream::UserSources, s_EmptySources); @@ -882,6 +886,7 @@ TEST_CASE("RepoSources_GroupPolicy_AdditionalSources", "[sources][groupPolicy]") REQUIRE_POLICY_EXCEPTION( RemoveSource(policySource.Name, progress), TogglePolicy::Policy::AdditionalSources); + REQUIRE_FALSE(removeCalledOnFactory); } SECTION("Additional source overrides default") { @@ -974,15 +979,17 @@ TEST_CASE("RepoSources_GroupPolicy_AllowedSources", "[sources][groupPolicy]") policies.SetValue<ValuePolicy::AllowedSources>({ policySource }); SetSetting(Stream::UserSources, s_EmptySources); - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; - ProgressCallback progress; SourceDetails details; details.Name = "notAllowed"; details.Type = "type"; details.Arg = "arg"; + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + REQUIRE_POLICY_EXCEPTION( AddSource(details, progress), TogglePolicy::Policy::AllowedSources); @@ -999,15 +1006,17 @@ TEST_CASE("RepoSources_GroupPolicy_AllowedSources", "[sources][groupPolicy]") { SetSetting(Stream::UserSources, s_EmptySources); - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; - ProgressCallback progress; SourceDetails details; details.Name = "name"; details.Type = "type"; details.Arg = "arg"; + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + REQUIRE_POLICY_EXCEPTION( AddSource(details, progress), TogglePolicy::Policy::AllowedSources); @@ -1039,10 +1048,9 @@ TEST_CASE("RepoSources_OpenMultipleWithSingleFailure", "[sources]") ProgressCallback progress; auto result = OpenSource("", progress); - REQUIRE(result.Source); - REQUIRE(result.SourcesWithUpdateFailure.empty()); + REQUIRE(result); - SearchResult searchResult = result.Source->Search({}); + SearchResult searchResult = result.Search({}); REQUIRE(searchResult.Failures.size() == 1); diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yaml @@ -4,6 +4,7 @@ Name: AppInstaller Test Exe Installer Publisher: Microsoft Corporation AppMoniker: AICLITestExe License: Test +ProductCode: AppInstallerCliTest.TestExeInstaller Installers: - Arch: x64 Url: https://ThisIsNotUsed diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp @@ -96,9 +96,9 @@ namespace TestCommon return VersionManifest; } - std::shared_ptr<const ISource> TestPackageVersion::GetSource() const + Repository::Source TestPackageVersion::GetSource() const { - return Source.lock(); + return std::const_pointer_cast<TestPackageVersion::ISource>(Source.lock()); } TestPackageVersion::MetadataMap TestPackageVersion::GetMetadata() const @@ -219,6 +219,11 @@ namespace TestCommon bool TestPackage::IsSame(const IPackage* other) const { + if (IsSameOverride) + { + return IsSameOverride(this, other); + } + const TestPackage* otherAvailable = dynamic_cast<const TestPackage*>(other); if (!otherAvailable || @@ -249,6 +254,11 @@ namespace TestCommon return Details.Identifier; } + SourceInformation TestSource::GetInformation() const + { + return Information; + } + SearchResult TestSource::Search(const SearchRequest& request) const { if (SearchFunction) @@ -266,9 +276,16 @@ namespace TestCommon return Composite; } - std::shared_ptr<ISource> TestSourceFactory::Create(const SourceDetails& details, IProgressCallback&) + std::shared_ptr<ISourceReference> TestSourceFactory::Create(const SourceDetails& details) { - return OnCreate(details); + if (OnOpenWithCustomHeader) + { + return std::make_shared<TestSourceReference>(details, OnOpenWithCustomHeader); + } + else + { + return std::make_shared<TestSourceReference>(details, OnOpen); + } } bool TestSourceFactory::Add(SourceDetails& details, IProgressCallback&) @@ -303,4 +320,39 @@ namespace TestCommon { return [this]() { return std::make_unique<TestSourceFactory>(*this); }; } + + bool AddSource(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback& progress) + { + Repository::Source source{ details.Name, details.Arg, details.Type }; + return source.Add(progress); + } + + bool UpdateSource(std::string_view name, AppInstaller::IProgressCallback& progress) + { + Repository::Source source{ name }; + return source.Update(progress).empty(); + } + + bool RemoveSource(std::string_view name, AppInstaller::IProgressCallback& progress) + { + Repository::Source source{ name }; + return source.Remove(progress); + } + + std::vector<AppInstaller::Repository::SourceDetails> GetSources() + { + return Repository::Source::GetCurrentSources(); + } + + AppInstaller::Repository::Source OpenSource(std::string_view name, AppInstaller::IProgressCallback& progress) + { + Repository::Source source{ name }; + source.Open(progress); + return source; + } + + void DropSource(std::string_view name) + { + Source::DropSource(name); + } } diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include <Public/AppInstallerRepositorySource.h> +#include <ISource.h> #include <winget/Manifest.h> #include <SourceFactory.h> @@ -30,7 +30,7 @@ namespace TestCommon LocIndString GetProperty(AppInstaller::Repository::PackageVersionProperty property) const override; std::vector<LocIndString> GetMultiProperty(AppInstaller::Repository::PackageVersionMultiProperty property) const override; Manifest GetManifest() override; - std::shared_ptr<const ISource> GetSource() const override; + AppInstaller::Repository::Source GetSource() const override; MetadataMap GetMetadata() const override; Manifest VersionManifest; @@ -71,6 +71,7 @@ namespace TestCommon std::shared_ptr<AppInstaller::Repository::IPackageVersion> InstalledVersion; std::vector<std::shared_ptr<AppInstaller::Repository::IPackageVersion>> AvailableVersions; + std::function<bool(const IPackage*, const IPackage*)> IsSameOverride; }; // An ISource implementation for use across the test code. @@ -78,10 +79,13 @@ namespace TestCommon { const AppInstaller::Repository::SourceDetails& GetDetails() const override; const std::string& GetIdentifier() const override; + AppInstaller::Repository::SourceInformation GetInformation() const override; + AppInstaller::Repository::SearchResult Search(const AppInstaller::Repository::SearchRequest& request) const override; bool IsComposite() const override; AppInstaller::Repository::SourceDetails Details = { "TestSource", "Microsoft.TestSource", "//arg", "", "*TestSource" }; + AppInstaller::Repository::SourceInformation Information; std::function<AppInstaller::Repository::SearchResult(const AppInstaller::Repository::SearchRequest& request)> SearchFunction; bool Composite = false; @@ -89,18 +93,53 @@ namespace TestCommon TestSource(const AppInstaller::Repository::SourceDetails& details) : Details(details) {} }; + struct TestSourceReference : public AppInstaller::Repository::ISourceReference + { + using OpenFunctor = std::function<std::shared_ptr<AppInstaller::Repository::ISource>(const AppInstaller::Repository::SourceDetails&)>; + using OpenFunctorWithCustomHeader = std::function<std::shared_ptr<AppInstaller::Repository::ISource>(const AppInstaller::Repository::SourceDetails&, std::optional<std::string>)>; + + TestSourceReference(const AppInstaller::Repository::SourceDetails& details, OpenFunctor open) : m_details(details), m_onOpen(open) {} + TestSourceReference(const AppInstaller::Repository::SourceDetails& details, OpenFunctorWithCustomHeader open) : m_details(details), m_onOpenWithCustomHeader(open) {} + + std::string GetIdentifier() override { return m_details.Identifier; } + + AppInstaller::Repository::SourceDetails& GetDetails() override { return m_details; }; + + bool SetCustomHeader(std::optional<std::string> header) override { m_header = header; return true; } + + std::shared_ptr<AppInstaller::Repository::ISource> Open(AppInstaller::IProgressCallback&) override + { + if (m_onOpenWithCustomHeader) + { + return m_onOpenWithCustomHeader(m_details, m_header); + } + else + { + return m_onOpen(m_details); + } + } + + private: + AppInstaller::Repository::SourceDetails m_details; + OpenFunctor m_onOpen; + OpenFunctorWithCustomHeader m_onOpenWithCustomHeader; + std::optional<std::string> m_header; + }; + // An ISourceFactory implementation for use across the test code. struct TestSourceFactory : public AppInstaller::Repository::ISourceFactory { - using CreateFunctor = std::function<std::shared_ptr<AppInstaller::Repository::ISource>(const AppInstaller::Repository::SourceDetails&)>; + using OpenFunctor = std::function<std::shared_ptr<AppInstaller::Repository::ISource>(const AppInstaller::Repository::SourceDetails&)>; + using OpenFunctorWithCustomHeader = std::function<std::shared_ptr<AppInstaller::Repository::ISource>(const AppInstaller::Repository::SourceDetails&, std::optional<std::string>)>; using AddFunctor = std::function<void(AppInstaller::Repository::SourceDetails&)>; using UpdateFunctor = std::function<void(const AppInstaller::Repository::SourceDetails&)>; using RemoveFunctor = std::function<void(const AppInstaller::Repository::SourceDetails&)>; - TestSourceFactory(CreateFunctor create) : OnCreate(std::move(create)) {} + TestSourceFactory(OpenFunctor open) : OnOpen(std::move(open)) {} + TestSourceFactory(OpenFunctorWithCustomHeader open) : OnOpenWithCustomHeader(std::move(open)) {} // ISourceFactory - std::shared_ptr<AppInstaller::Repository::ISource> Create(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; + std::shared_ptr<AppInstaller::Repository::ISourceReference> Create(const AppInstaller::Repository::SourceDetails& details) override; bool Add(AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; bool Update(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; bool Remove(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; @@ -108,9 +147,17 @@ namespace TestCommon // Make copies of self when requested. operator std::function<std::unique_ptr<AppInstaller::Repository::ISourceFactory>()>(); - CreateFunctor OnCreate; + OpenFunctor OnOpen; + OpenFunctorWithCustomHeader OnOpenWithCustomHeader; AddFunctor OnAdd; UpdateFunctor OnUpdate; RemoveFunctor OnRemove; }; + + bool AddSource(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback& progress); + bool UpdateSource(std::string_view name, AppInstaller::IProgressCallback& progress); + bool RemoveSource(std::string_view name, AppInstaller::IProgressCallback& progress); + AppInstaller::Repository::Source OpenSource(std::string_view name, AppInstaller::IProgressCallback& progress); + void DropSource(std::string_view name); + std::vector<AppInstaller::Repository::SourceDetails> GetSources(); } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -22,8 +22,7 @@ #include <Workflows/SourceFlow.h> #include <Workflows/ShellExecuteInstallerHandler.h> #include <Workflows/WorkflowBase.h> -#include <Public/AppInstallerRepositorySource.h> -#include <Public/AppInstallerRepositorySearch.h> +#include <Public/winget/RepositorySource.h> #include <Commands/ExportCommand.h> #include <Commands/ImportCommand.h> #include <Commands/InstallCommand.h> @@ -82,7 +81,7 @@ namespace auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); result.Matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector<Manifest>{ manifest }, const_cast<WorkflowTestSource*>(this)->shared_from_this()), + TestPackage::Make(std::vector<Manifest>{ manifest }, shared_from_this()), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnOne"))); } else if (input == "TestQueryReturnTwo") @@ -90,13 +89,13 @@ namespace auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); result.Matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector<Manifest>{ manifest }, const_cast<WorkflowTestSource*>(this)->shared_from_this()), + TestPackage::Make(std::vector<Manifest>{ manifest }, shared_from_this()), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); auto manifest2 = YamlParser::CreateFromPath(TestDataFile("Manifest-Good.yaml")); result.Matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector<Manifest>{ manifest2 }, const_cast<WorkflowTestSource*>(this)->shared_from_this()), + TestPackage::Make(std::vector<Manifest>{ manifest2 }, shared_from_this()), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); } @@ -128,24 +127,27 @@ namespace } // Empty query should return all exe, msix and msstore installer - if (input.empty() || input == "AppInstallerCliTest.TestExeInstaller") + if (input.empty() || CaseInsensitiveEquals(input, "AppInstallerCliTest.TestExeInstaller")) { auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); auto manifest3 = YamlParser::CreateFromPath(TestDataFile(m_upgradeUsesLicenses ? "UpdateFlowTest_Exe_2_LicenseAgreement.yaml" : "UpdateFlowTest_Exe_2.yaml")); + auto testPackage = + TestPackage::Make( + manifest, + TestPackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector<Manifest>{ manifest3, manifest2, manifest }, + shared_from_this() + ); + testPackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; result.Matches.emplace_back( ResultMatch( - TestPackage::Make( - manifest, - TestPackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Exe" }, - { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, - { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, - }, - std::vector<Manifest>{ manifest3, manifest2, manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() - ), + testPackage, PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } @@ -159,7 +161,7 @@ namespace manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); } @@ -173,7 +175,7 @@ namespace manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "MSStore" } }, std::vector<Manifest>{ manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMSStoreInstaller"))); } @@ -188,7 +190,7 @@ namespace manifest2, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } @@ -203,7 +205,7 @@ namespace manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } @@ -215,7 +217,7 @@ namespace ResultMatch( TestPackage::Make( std::vector<Manifest>{ manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } @@ -235,7 +237,7 @@ namespace { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, }, std::vector<Manifest>{ manifest2, manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller.Dependencies"))); } @@ -247,7 +249,7 @@ namespace ResultMatch( TestPackage::Make( std::vector<Manifest>{ manifest }, - const_cast<WorkflowTestCompositeSource*>(this)->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller.WFDep"))); } @@ -262,7 +264,7 @@ namespace manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest }, - this->shared_from_this() + shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestInstallerWithLicenseAgreement"))); } @@ -367,7 +369,7 @@ void OverrideForOpenSource(TestContext& context) { context.Override({ "OpenSource", [](TestContext& context) { - context.Add<Execution::Data::Source>(std::make_shared<WorkflowTestSource>()); + context.Add<Execution::Data::Source>(Source{ std::make_shared<WorkflowTestSource>() }); } }); } @@ -379,20 +381,21 @@ void OverrideForCompositeInstalledSource(TestContext& context, bool upgradeUsesL context.Override({ "OpenCompositeSource", [=](TestContext& context) { - context.Add<Execution::Data::Source>(std::make_shared<WorkflowTestCompositeSource>(upgradeUsesLicenses)); + context.Add<Execution::Data::Source>(Source{ std::make_shared<WorkflowTestCompositeSource>(upgradeUsesLicenses) }); } }); } -void OverrideForImportSource(TestContext& context) +void OverrideForImportSource(TestContext& context, bool useTestCompositeSource = false) { - context.Override({ "OpenPredefinedSource", [](TestContext& context) + context.Override({ "OpenPredefinedSource", [=](TestContext& context) { - context.Add<Execution::Data::Source>({}); + auto installedSource = useTestCompositeSource? std::make_shared<WorkflowTestCompositeSource>(false) : std::make_shared<TestSource>(); + context.Add<Execution::Data::Source>(Source{ installedSource }); } }); context.Override({ Workflow::OpenSourcesForImport, [](TestContext& context) { - context.Add<Execution::Data::Sources>(std::vector<std::shared_ptr<ISource>>{ std::make_shared<WorkflowTestCompositeSource>(false) }); + context.Add<Execution::Data::Sources>(std::vector<Source>{ Source{ std::make_shared<WorkflowTestCompositeSource>(false) } }); } }); } @@ -400,12 +403,12 @@ void OverrideOpenSourceForDependencies(TestContext& context) { context.Override({ "OpenSource", [](TestContext& context) { - context.Add<Execution::Data::Source>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::Source>(Source{ std::make_shared<DependenciesTestSource>() }); } }); context.Override({ Workflow::OpenDependencySource, [](TestContext& context) { - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); } }); } @@ -413,7 +416,7 @@ void OverrideDependencySource(TestContext& context) { context.Override({ Workflow::OpenDependencySource, [](TestContext& context) { - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); } }); } @@ -577,24 +580,27 @@ void OverrideForMSStore(TestContext& context, bool isUpdate) } }); } -void OverrideForSourceAddWithAgreements(TestContext& context) +void OverrideForSourceAddWithAgreements(TestContext& context, bool isAddExpected = true) { context.Override({ EnsureRunningAsAdmin, [](TestContext&) { } }); - context.Override({ AddSource, [](TestContext&) + if (isAddExpected) { - } }); + context.Override({ AddSource, [](TestContext&) + { + } }); + } - context.Override({ OpenSourceForSourceAdd, [](TestContext& context) + context.Override({ CreateSourceForSourceAdd, [](TestContext& context) { auto testSource = std::make_shared<TestSource>(); - testSource->Details.Information.SourceAgreementsIdentifier = "AgreementsIdentifier"; - testSource->Details.Information.SourceAgreements.emplace_back("Agreement Label", "Agreement Text", "https://test"); - testSource->Details.Information.RequiredPackageMatchFields.emplace_back("Market"); - testSource->Details.Information.RequiredQueryParameters.emplace_back("Market"); - context << Workflow::HandleSourceAgreements(testSource); + testSource->Information.SourceAgreementsIdentifier = "AgreementsIdentifier"; + testSource->Information.SourceAgreements.emplace_back("Agreement Label", "Agreement Text", "https://test"); + testSource->Information.RequiredPackageMatchFields.emplace_back("Market"); + testSource->Information.RequiredQueryParameters.emplace_back("Market"); + context << Workflow::HandleSourceAgreements(Source{ testSource }); } }); } @@ -602,7 +608,7 @@ void OverrideOpenDependencySource(TestContext& context) { context.Override({ Workflow::OpenDependencySource, [](TestContext& context) { - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); } }); } @@ -1128,7 +1134,7 @@ TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGr OverrideOpenDependencySource(context); OverrideForInstallMultiplePackages(context); - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); context.Add<Execution::Data::Manifest>(manifest); context.Add<Execution::Data::Installer>(manifest.Installers[0]); @@ -1152,7 +1158,7 @@ TEST_CASE("DependencyGraph_validMinVersions", "[InstallFlow][workflow][dependenc OverrideOpenDependencySource(context); OverrideForInstallMultiplePackages(context); - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); context.Add<Execution::Data::Manifest>(manifest); context.Add<Execution::Data::Installer>(manifest.Installers[0]); @@ -1180,7 +1186,7 @@ TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph OverrideOpenDependencySource(context); OverrideForInstallMultiplePackages(context); - context.Add<Execution::Data::DependencySource>(std::make_shared<DependenciesTestSource>()); + context.Add<Execution::Data::DependencySource>(Source{ std::make_shared<DependenciesTestSource>() }); context.Add<Execution::Data::Manifest>(manifest); context.Add<Execution::Data::Installer>(manifest.Installers[0]); @@ -1778,7 +1784,7 @@ TEST_CASE("ImportFlow_PackageAlreadyInstalled", "[ImportFlow][workflow]") std::ostringstream importOutput; TestContext context{ importOutput, std::cin }; - OverrideForImportSource(context); + OverrideForImportSource(context, true); context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-AlreadyInstalled.json").GetPath().string()); ImportCommand importCommand({}); @@ -2148,7 +2154,7 @@ TEST_CASE("InstallFlowMultiLocale_PreferenceWithBetterLocale", "[InstallFlow][wo REQUIRE(installResultStr.find("/en-GB") != std::string::npos); } -// TODO +// TODO: // add dependencies for installer tests to DependenciesTestSource (or a new one) // add tests for min version dependency solving // add tests that check for correct installation of dependencies (not only the order) @@ -2208,14 +2214,7 @@ TEST_CASE("SourceAddFlow_Agreement_Prompt_No", "[SourceAddFlow][workflow]") std::istringstream sourceAddInput{ "n" }; std::ostringstream sourceAddOutput; TestContext context{ sourceAddOutput, sourceAddInput }; - OverrideForSourceAddWithAgreements(context); - // This tests RemoveSource is called after agreement is not accepted. If they are not called, the test fails with unused override. - context.Override({ GetSourceListWithFilter, [](TestContext&) - { - } }); - context.Override({ RemoveSources, [](TestContext&) - { - } }); + OverrideForSourceAddWithAgreements(context, false); context.Args.AddArg(Execution::Args::Type::SourceName, "TestSource"sv); context.Args.AddArg(Execution::Args::Type::SourceType, "Microsoft.Test"sv); context.Args.AddArg(Execution::Args::Type::SourceArg, "TestArg"sv); @@ -2367,11 +2366,16 @@ TEST_CASE("OpenSource_WithCustomHeader", "[OpenSource][CustomHeader]") details.Type = "Microsoft.Rest"; details.Arg = "thisIsTheArg"; details.Data = "thisIsTheData"; - details.CustomHeader = "CustomHeader"; + + std::string customHeader = "Test custom header in Open source Flow"; bool receivedCustomHeader = false; - TestSourceFactory factory { [&](const SourceDetails& sd) { return std::shared_ptr<ISource>(new TestSource(sd)); } }; - factory.OnAdd = [&](SourceDetails& sd) { receivedCustomHeader = details.CustomHeader.value().compare(sd.CustomHeader.value()) == 0; }; + TestSourceFactory factory{ + [&](const SourceDetails& sd, std::optional<std::string> header) + { + receivedCustomHeader = header.value() == customHeader; + return std::shared_ptr<ISource>(new TestSource(sd)); + } }; TestHook_SetSourceFactoryOverride(details.Type, factory); TestProgress progress; @@ -2380,14 +2384,11 @@ TEST_CASE("OpenSource_WithCustomHeader", "[OpenSource][CustomHeader]") std::ostringstream output; TestContext context{ output, std::cin }; context.Args.AddArg(Execution::Args::Type::Query, "TestQuery"sv); - - std::string customHeader2 = "Test custom header in Open source Flow"; - context.Args.AddArg(Execution::Args::Type::CustomHeader, customHeader2); + context.Args.AddArg(Execution::Args::Type::CustomHeader, customHeader); context.Args.AddArg(Execution::Args::Type::Source, details.Name); AppInstaller::CLI::Workflow::OpenSource()(context); - auto source = context.Get<Execution::Data::Source>(); - REQUIRE(source.get()->GetDetails().CustomHeader.value_or("").compare(customHeader2) == 0); + REQUIRE(receivedCustomHeader); } TEST_CASE("AdminSetting_LocalManifestFiles", "[LocalManifests][workflow]") diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -230,8 +230,8 @@ </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="CompositeSource.h" /> - <ClInclude Include="DependenciesTable.h" /> <ClInclude Include="ICU\SQLiteICU.h" /> + <ClInclude Include="ISource.h" /> <ClInclude Include="Microsoft\ARPHelper.h" /> <ClInclude Include="Microsoft\PredefinedInstalledSourceFactory.h" /> <ClInclude Include="Microsoft\PredefinedWriteableSourceFactory.h" /> @@ -268,6 +268,8 @@ <ClInclude Include="Microsoft\ConfigurableTestSourceFactory.h" /> <ClInclude Include="pch.h" /> <ClInclude Include="Public\winget\PackageTrackingCatalog.h" /> + <ClInclude Include="Public\winget\RepositorySearch.h" /> + <ClInclude Include="Public\winget\RepositorySource.h" /> <ClInclude Include="Rest\RestClient.h" /> <ClInclude Include="Rest\RestSource.h" /> <ClInclude Include="Rest\RestSourceFactory.h" /> @@ -288,8 +290,6 @@ <ClInclude Include="SourceList.h" /> <ClInclude Include="SourcePolicy.h" /> <ClInclude Include="SQLiteStatementBuilder.h" /> - <ClInclude Include="Public\AppInstallerRepositorySearch.h" /> - <ClInclude Include="Public\AppInstallerRepositorySource.h" /> <ClInclude Include="SQLiteTempTable.h" /> <ClInclude Include="SQLiteWrapper.h" /> </ItemGroup> @@ -330,6 +330,7 @@ <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> + <ClCompile Include="RepositorySearch.cpp" /> <ClCompile Include="RepositorySource.cpp" /> <ClCompile Include="Rest\RestClient.cpp" /> <ClCompile Include="Rest\RestSource.cpp" /> diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -56,7 +56,7 @@ <UniqueIdentifier>{2cc20cdb-dcb2-4e0e-b04f-e2d838146100}</UniqueIdentifier> </Filter> <Filter Include="Public\winget"> - <UniqueIdentifier>{4459e669-cc20-4550-9f12-53e088e9170e}</UniqueIdentifier> + <UniqueIdentifier>{aa7315bc-4eb0-4280-9572-f5a25f6e73ad}</UniqueIdentifier> </Filter> </ItemGroup> <ItemGroup> @@ -117,12 +117,6 @@ <ClInclude Include="SQLiteStatementBuilder.h"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="Public\AppInstallerRepositorySource.h"> - <Filter>Public</Filter> - </ClInclude> - <ClInclude Include="Public\AppInstallerRepositorySearch.h"> - <Filter>Public</Filter> - </ClInclude> <ClInclude Include="SourceFactory.h"> <Filter>Header Files</Filter> </ClInclude> @@ -216,9 +210,6 @@ <ClInclude Include="Microsoft\Schema\1_3\HashVirtualTable.h"> <Filter>Microsoft\Schema\1_3</Filter> </ClInclude> - <ClInclude Include="DependenciesTable.h"> - <Filter>Microsoft\Schema\1_3</Filter> - </ClInclude> <ClInclude Include="SourceList.h"> <Filter>Header Files</Filter> </ClInclude> @@ -249,6 +240,15 @@ <ClInclude Include="Public\winget\PackageTrackingCatalog.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\RepositorySearch.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="Public\winget\RepositorySource.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="ISource.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -386,6 +386,9 @@ <ClCompile Include="PackageTrackingCatalog.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="RepositorySearch.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -335,9 +335,7 @@ namespace AppInstaller::Repository { auto itr = std::find_if(Failures.begin(), Failures.end(), [&failure](const SearchResult::Failure& present) { - const auto& presentDetails = present.Source->GetDetails(); - const auto& incomingDetails = failure.Source->GetDetails(); - return (presentDetails.Identifier == incomingDetails.Identifier && presentDetails.Type == incomingDetails.Type); + return present.SourceName == failure.SourceName; }); if (itr == Failures.end()) @@ -405,7 +403,6 @@ namespace AppInstaller::Repository CompositeSource::CompositeSource(std::string identifier) { - m_details.Name = "CompositeSource"; m_details.Identifier = std::move(identifier); } @@ -501,7 +498,7 @@ namespace AppInstaller::Repository } catch (...) { - if (result.AddFailureIfSourceNotPresent({ source, std::current_exception() })) + if (result.AddFailureIfSourceNotPresent({ source->GetDetails().Name, std::current_exception() })) { LOG_CAUGHT_EXCEPTION(); AICLI_LOG(Repo, Warning, << "Failed to search source for correlation: " << source->GetDetails().Name); @@ -593,7 +590,7 @@ namespace AppInstaller::Repository { LOG_CAUGHT_EXCEPTION(); AICLI_LOG(Repo, Warning, << "Failed to search source: " << source->GetDetails().Name); - result.AddFailureIfSourceNotPresent({ source, std::current_exception() }); + result.AddFailureIfSourceNotPresent({ source->GetDetails().Name, std::current_exception() }); } // Move failures into the single result @@ -607,6 +604,12 @@ namespace AppInstaller::Repository // Check for a package already in the result that should have been correlated already. auto packageData = result.CheckForExistingResultFromAvailablePackageMatch(match); + // If found existing package in the result, continue + if (!packageData) + { + continue; + } + // If no package was found that was already in the results, do a correlation lookup with the installed // source to create a new composite package entry if we find any packages there. bool foundInstalledMatch = false; @@ -655,6 +658,7 @@ namespace AppInstaller::Repository if (installedPackage && !result.ContainsInstalledPackage(installedPackage.get())) { + // TODO: Needs a whole separate change to fix the fact that we don't support multiple available packages and what the different search behaviors mean foundInstalledMatch = true; result.Matches.emplace_back(std::make_shared<CompositePackage>(std::move(installedPackage), std::move(match.Package)), match.MatchCriteria); } @@ -697,7 +701,7 @@ namespace AppInstaller::Repository { LOG_CAUGHT_EXCEPTION(); AICLI_LOG(Repo, Warning, << "Failed to search source: " << source->GetDetails().Name); - result.Failures.emplace_back(SearchResult::Failure{ source, std::current_exception() }); + result.Failures.emplace_back(SearchResult::Failure{ source->GetDetails().Name, std::current_exception() }); } // Move into the single result diff --git a/src/AppInstallerRepositoryCore/CompositeSource.h b/src/AppInstallerRepositoryCore/CompositeSource.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once -#include "AppInstallerRepositorySource.h" +#include "ISource.h" namespace AppInstaller::Repository { @@ -62,5 +62,3 @@ namespace AppInstaller::Repository CompositeSearchBehavior m_searchBehavior; }; } - - diff --git a/src/AppInstallerRepositoryCore/ISource.h b/src/AppInstallerRepositoryCore/ISource.h @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Public/winget/RepositorySource.h" + +namespace AppInstaller::Repository +{ + // Internal interface for interacting with a source from outside of the repository lib. + struct ISource + { + virtual ~ISource() = default; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names unless the source is internal to winget, + // in which case the identifier should begin with a '*' character. + virtual const std::string& GetIdentifier() const = 0; + + // Get the source's configuration from settings. Source details can be used during opening the source. + virtual const SourceDetails& GetDetails() const = 0; + + // Get the source's information after the source is opened. + virtual SourceInformation GetInformation() const { return {}; }; + + // Execute a search on the source. + virtual SearchResult Search(const SearchRequest& request) const = 0; + + // Gets a value indicating whether this source is a composite of other sources, + // and thus the packages may come from disparate sources as well. + virtual bool IsComposite() const { return false; } + + // Gets the available sources if the source is composite. + virtual std::vector<std::shared_ptr<ISource>> GetAvailableSources() const { return {}; } + }; + + // Internal interface to represents source information; basically SourceDetails but with methods to enable differential behaviors. + struct ISourceReference + { + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names unless the source is internal to winget, + // in which case the identifier should begin with a '*' character. + virtual std::string GetIdentifier() = 0; + + // Get the source's configuration details from settings. + virtual SourceDetails& GetDetails() = 0; + + // Get the source's information. + virtual SourceInformation GetInformation() { return {}; } + + // Set custom header. Returns false if custom header is not supported. + virtual bool SetCustomHeader(std::optional<std::string> header) { UNREFERENCED_PARAMETER(header); return false; } + + // Opens the source. This function should throw upon open failure rather than returning an empty pointer. + virtual std::shared_ptr<ISource> Open(IProgressCallback& progress) = 0; + }; + + // Internal interface extension to ISource for databases that can be updated after creation, like InstallingPackages + struct IMutablePackageSource + { + virtual ~IMutablePackageSource() = default; + + // Adds a package version to the source. + virtual void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; + + // Removes a package version from the source. + virtual void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.cpp @@ -12,8 +12,6 @@ namespace AppInstaller::Repository::Microsoft { namespace { - static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageFileName = "source.msix"sv; - // The configuration defined for a source. // This can be added to as new scenarios are needed for testing. struct TestSourceConfiguration @@ -29,7 +27,7 @@ namespace AppInstaller::Repository::Microsoft if (reader->parse(config.c_str(), config.c_str() + config.size(), &root, &error)) { // TODO: If this becomes more dynamic, refactor the UserSettings code to make it easier to leverage here - ParseHR(root, ".CreateHR", CreateHR); + ParseHR(root, ".OpenHR", OpenHR); ParseHR(root, ".SearchHR", SearchHR); } else @@ -40,7 +38,7 @@ namespace AppInstaller::Repository::Microsoft } // The HR to throw on Factory::Create (if FAILED) - HRESULT CreateHR = S_OK; + HRESULT OpenHR = S_OK; // The HR to throw on Source::Search (if FAILED) HRESULT SearchHR = S_OK; @@ -85,15 +83,37 @@ namespace AppInstaller::Repository::Microsoft TestSourceConfiguration m_config; }; + struct ConfigurableTestSourceReference : public ISourceReference + { + ConfigurableTestSourceReference(const SourceDetails& details) : m_details(details) + { + m_details.Identifier = "*ConfigurableTestSource"; + } + + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + bool SetCustomHeader(std::optional<std::string>) override { return true; } + + std::shared_ptr<ISource> Open(IProgressCallback&) override + { + // enables `source add` with FAILED(OpenHR) + TestSourceConfiguration config{ m_details.Arg }; + THROW_IF_FAILED(config.OpenHR); + return std::make_shared<ConfigurableTestSource>(m_details, config); + } + + private: + SourceDetails m_details; + }; + // The actual factory implementation. struct ConfigurableTestSourceFactoryImpl : public ISourceFactory { - std::shared_ptr<ISource> Create(const SourceDetails& details, IProgressCallback&) override final + std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final { - // Allow the custom header to override the arg (enables `source add` with FAILED(CreateHR)) - TestSourceConfiguration config{ details.CustomHeader.value_or(details.Arg) }; - THROW_IF_FAILED(config.CreateHR); - return std::make_shared<ConfigurableTestSource>(details, config); + return std::make_shared<ConfigurableTestSourceReference>(details); } bool Add(SourceDetails& details, IProgressCallback&) override final diff --git a/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "Public/AppInstallerRepositorySource.h" +#include "ISource.h" #include "SourceFactory.h" #include <string_view> diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -52,20 +52,15 @@ namespace AppInstaller::Repository::Microsoft // The base class for a package that comes from a preindexed packaged source. struct PreIndexedFactoryBase : public ISourceFactory { - std::shared_ptr<ISource> Create(const SourceDetails& details, IProgressCallback& progress) override final + std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final { - THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); + // With more than one source implementation, we will probably need to probe first + THROW_HR_IF(E_INVALIDARG, !details.Type.empty() && details.Type != PreIndexedPackageSourceFactory::Type()); - auto lock = Synchronization::CrossProcessReaderWriteLock::LockShared(CreateNameForCPRWL(details), progress); - if (!lock) - { - return {}; - } - - return CreateInternal(details, std::move(lock), progress); + return CreateInternal(details); } - virtual std::shared_ptr<ISource> CreateInternal(const SourceDetails& details, Synchronization::CrossProcessReaderWriteLock&& lock, IProgressCallback& progress) = 0; + virtual std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) = 0; bool Add(SourceDetails& details, IProgressCallback& progress) override final { @@ -174,22 +169,39 @@ namespace AppInstaller::Repository::Microsoft } }; - // Source factory for running within a packaged context - struct PackagedContextFactory : public PreIndexedFactoryBase + // *Should only be called when under a CrossProcessReaderWriteLock* + std::optional<Deployment::Extension> GetExtensionFromDetails(const SourceDetails& details) + { + Deployment::ExtensionCatalog catalog(Deployment::SourceExtensionName); + return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); + } + + struct PackagedContextSourceReference : public ISourceReference { - // *Should only be called when under a CrossProcessReaderWriteLock* - std::optional<Deployment::Extension> GetExtensionFromDetails(const SourceDetails& details) + PackagedContextSourceReference(const SourceDetails& details) : m_details(details) { - Deployment::ExtensionCatalog catalog(Deployment::SourceExtensionName); - return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); + if (!m_details.Data.empty()) + { + m_details.Identifier = GetPackageFamilyNameFromDetails(details); + } } - std::shared_ptr<ISource> CreateInternal(const SourceDetails& details, Synchronization::CrossProcessReaderWriteLock&& lock, IProgressCallback& progress) override + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + std::shared_ptr<ISource> Open(IProgressCallback& progress) override { - auto extension = GetExtensionFromDetails(details); + auto lock = Synchronization::CrossProcessReaderWriteLock::LockShared(CreateNameForCPRWL(m_details), progress); + if (!lock) + { + return {}; + } + + auto extension = GetExtensionFromDetails(m_details); if (!extension) { - AICLI_LOG(Repo, Info, << "Package not found " << details.Data); + AICLI_LOG(Repo, Info, << "Package not found " << m_details.Data); THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); } @@ -206,7 +218,20 @@ namespace AppInstaller::Repository::Microsoft // We didn't use to store the source identifier, so we compute it here in case it's // missing from the details. - return std::make_shared<SQLiteIndexSource>(details, GetPackageFamilyNameFromDetails(details), std::move(index), std::move(lock)); + m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); + return std::make_shared<SQLiteIndexSource>(m_details, std::move(index), std::move(lock)); + } + + private: + SourceDetails m_details; + }; + + // Source factory for running within a packaged context + struct PackagedContextFactory : public PreIndexedFactoryBase + { + std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) override + { + return std::make_shared<PackagedContextSourceReference>(details); } bool UpdateInternal(const std::string& packageLocation, Msix::MsixInfo& packageInfo, const SourceDetails& details, IProgressCallback& progress) override @@ -304,21 +329,38 @@ namespace AppInstaller::Repository::Microsoft } }; - // Source factory for running outside of a package. - struct DesktopContextFactory : public PreIndexedFactoryBase + // Constructs the location that we will write files to. + std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) + { + std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); + result /= PreIndexedPackageSourceFactory::Type(); + result /= GetPackageFamilyNameFromDetails(details); + return result; + } + + struct DesktopContextSourceReference : public ISourceReference { - // Constructs the location that we will write files to. - std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) + DesktopContextSourceReference(const SourceDetails& details) : m_details(details) { - std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); - result /= PreIndexedPackageSourceFactory::Type(); - result /= GetPackageFamilyNameFromDetails(details); - return result; + if (!m_details.Data.empty()) + { + m_details.Identifier = GetPackageFamilyNameFromDetails(details); + } } - std::shared_ptr<ISource> CreateInternal(const SourceDetails& details, Synchronization::CrossProcessReaderWriteLock&& lock, IProgressCallback&) override + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + std::shared_ptr<ISource> Open(IProgressCallback& progress) override { - std::filesystem::path packageLocation = GetStatePathFromDetails(details); + auto lock = Synchronization::CrossProcessReaderWriteLock::LockShared(CreateNameForCPRWL(m_details), progress); + if (!lock) + { + return {}; + } + + std::filesystem::path packageLocation = GetStatePathFromDetails(m_details); packageLocation /= s_PreIndexedPackageSourceFactory_IndexFileName; if (!std::filesystem::exists(packageLocation)) @@ -331,7 +373,20 @@ namespace AppInstaller::Repository::Microsoft // We didn't use to store the source identifier, so we compute it here in case it's // missing from the details. - return std::make_shared<SQLiteIndexSource>(details, GetPackageFamilyNameFromDetails(details), std::move(index), std::move(lock)); + m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); + return std::make_shared<SQLiteIndexSource>(m_details, std::move(index), std::move(lock)); + } + + private: + SourceDetails m_details; + }; + + // Source factory for running outside of a package. + struct DesktopContextFactory : public PreIndexedFactoryBase + { + std::shared_ptr<ISourceReference> CreateInternal(const SourceDetails& details) override + { + return std::make_shared<DesktopContextSourceReference>(details); } bool UpdateInternal(const std::string&, Msix::MsixInfo& packageInfo, const SourceDetails& details, IProgressCallback& progress) override diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "Public/AppInstallerRepositorySource.h" +#include "ISource.h" #include "SourceFactory.h" #include <string_view> diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp @@ -97,18 +97,24 @@ namespace AppInstaller::Repository::Microsoft } } - // The factory for the predefined installed source. - struct Factory : public ISourceFactory + struct PredefinedInstalledSourceReference : public ISourceReference { - std::shared_ptr<ISource> Create(const SourceDetails& details, IProgressCallback& progress) override final + PredefinedInstalledSourceReference(const SourceDetails& details) : m_details(details) + { + m_details.Identifier = "*PredefinedInstalledSource"; + } + + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + std::shared_ptr<ISource> Open(IProgressCallback& progress) override { // TODO: Maybe we do need to use it? UNREFERENCED_PARAMETER(progress); - THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedInstalledSourceFactory::Type()); - // Determine the filter - PredefinedInstalledSourceFactory::Filter filter = PredefinedInstalledSourceFactory::StringToFilter(details.Arg); + PredefinedInstalledSourceFactory::Filter filter = PredefinedInstalledSourceFactory::StringToFilter(m_details.Arg); AICLI_LOG(Repo, Info, << "Creating PredefinedInstalledSource with filter [" << PredefinedInstalledSourceFactory::FilterToString(filter) << ']'); // Create an in memory index @@ -127,7 +133,21 @@ namespace AppInstaller::Repository::Microsoft PopulateIndexFromMSIX(index); } - return std::make_shared<SQLiteIndexSource>(details, "*PredefinedInstalledSource", std::move(index), Synchronization::CrossProcessReaderWriteLock{}, true); + return std::make_shared<SQLiteIndexSource>(m_details, std::move(index), Synchronization::CrossProcessReaderWriteLock{}, true); + } + + private: + SourceDetails m_details; + }; + + // The factory for the predefined installed source. + struct Factory : public ISourceFactory + { + std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final + { + THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedInstalledSourceFactory::Type()); + + return std::make_shared<PredefinedInstalledSourceReference>(details); } bool Add(SourceDetails&, IProgressCallback&) override final diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "Public/AppInstallerRepositorySource.h" +#include "ISource.h" #include "SourceFactory.h" #include <string_view> diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp @@ -15,58 +15,75 @@ using namespace std::string_view_literals; namespace AppInstaller::Repository::Microsoft { - // The factory for the predefined installing source. - struct PredefinedWriteableSourceFactoryImpl : public ISourceFactory + namespace { - std::shared_ptr<ISource> Create(const SourceDetails& details, IProgressCallback& progress) override final; - - bool Add(SourceDetails&, IProgressCallback&) override final + // The factory for the predefined installing source. + struct PredefinedWriteableSourceFactoryImpl : public ISourceFactory { - // Add should never be needed, as this is predefined. - THROW_HR(E_NOTIMPL); - } + std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final; - bool Update(const SourceDetails&, IProgressCallback&) override final - { - // Update could be used later, but not for now. - THROW_HR(E_NOTIMPL); - } + bool Add(SourceDetails&, IProgressCallback&) override final + { + // Add should never be needed, as this is predefined. + THROW_HR(E_NOTIMPL); + } + + bool Update(const SourceDetails&, IProgressCallback&) override final + { + // Update could be used later, but not for now. + THROW_HR(E_NOTIMPL); + } + + bool Remove(const SourceDetails&, IProgressCallback&) override final + { + // Similar to add, remove should never be needed. + THROW_HR(E_NOTIMPL); + } + }; - bool Remove(const SourceDetails&, IProgressCallback&) override final + struct PredefinedWriteableSourceReference : public ISourceReference { - // Similar to add, remove should never be needed. - THROW_HR(E_NOTIMPL); - } + PredefinedWriteableSourceReference(const SourceDetails& details) : m_details(details) + { + m_details.Identifier = "*PredefinedWriteableSource"; + } - private: - static std::shared_ptr<ISource> g_sharedSource; - static std::once_flag g_InstallingSourceOnceFlag; - }; + std::string GetIdentifier() override { return m_details.Identifier; } - std::shared_ptr<ISource> PredefinedWriteableSourceFactoryImpl::g_sharedSource = nullptr; - std::once_flag PredefinedWriteableSourceFactoryImpl::g_InstallingSourceOnceFlag; + SourceDetails& GetDetails() override { return m_details; }; - std::shared_ptr<ISource> PredefinedWriteableSourceFactoryImpl::Create(const SourceDetails& details, IProgressCallback&) - { - THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedWriteableSourceFactory::Type()); + std::shared_ptr<ISource> Open(IProgressCallback&) override + { + // Installing is the only type right now so just return the Installing source to all callers. + // Since the source is writeable, it must be shared by all callers that try to open it + // since queries on one instance would not see what was written on another instance. + std::call_once(g_InstallingSourceOnceFlag, + [&]() + { + // Create an in memory index + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, Schema::Version::Latest()); - std::string lockName = "WriteableSource_"; + g_sharedSource = std::make_shared<SQLiteIndexWriteableSource>(m_details, std::move(index), Synchronization::CrossProcessReaderWriteLock{}, true); + }); - // Create an in memory index - SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, Schema::Version::Latest()); + return g_sharedSource; + } - // Installing is the only type right now so just return the Installing source to all callers. - // Since the source is writeable, it must be shared by all callers that try to open it - // since queries on one instance would not see what was written on another instance. - std::call_once(g_InstallingSourceOnceFlag, - [&]() - { - // Create an in memory index - SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, Schema::Version::Latest()); + private: + SourceDetails m_details; + static std::shared_ptr<ISource> g_sharedSource; + static std::once_flag g_InstallingSourceOnceFlag; + }; - g_sharedSource = std::make_shared<SQLiteIndexWriteableSource>(details, "*PredefinedWriteableSource", std::move(index), Synchronization::CrossProcessReaderWriteLock{}, true); - }); - return g_sharedSource; + std::shared_ptr<ISource> PredefinedWriteableSourceReference::g_sharedSource = nullptr; + std::once_flag PredefinedWriteableSourceReference::g_InstallingSourceOnceFlag; + + std::shared_ptr<ISourceReference> PredefinedWriteableSourceFactoryImpl::Create(const SourceDetails& details) + { + THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedWriteableSourceFactory::Type()); + + return std::make_shared<PredefinedWriteableSourceReference>(details); + } } std::string_view PredefinedWriteableSourceFactory::TypeToString(WriteableType type) diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "Public/AppInstallerRepositorySource.h" +#include "ISource.h" #include "SourceFactory.h" #include <string_view> diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h @@ -4,7 +4,7 @@ #include "SQLiteWrapper.h" #include "Microsoft/Schema/ISQLiteIndex.h" #include "Microsoft/Schema/Version.h" -#include "Public/AppInstallerRepositorySearch.h" +#include "ISource.h" #include <AppInstallerLanguageUtilities.h> #include <AppInstallerVersions.h> #include <winget/Manifest.h> diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -82,9 +82,9 @@ namespace AppInstaller::Repository::Microsoft return GetManifestFromArgAndRelativePath(source->GetDetails().Arg, relativePathOpt.value(), manifestSHA256); } - std::shared_ptr<const ISource> GetSource() const override + Source GetSource() const override { - return GetReferenceSource(); + return Source{ GetReferenceSource() }; } IPackageVersion::Metadata GetMetadata() const override @@ -346,10 +346,9 @@ namespace AppInstaller::Repository::Microsoft }; } - SQLiteIndexSource::SQLiteIndexSource(const SourceDetails& details, std::string identifier, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock, bool isInstalledSource) : + SQLiteIndexSource::SQLiteIndexSource(const SourceDetails& details, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock, bool isInstalledSource) : m_details(details), m_lock(std::move(lock)), m_isInstalled(isInstalledSource), m_index(std::move(index)) { - m_details.Identifier = std::move(identifier); } const SourceDetails& SQLiteIndexSource::GetDetails() const @@ -367,7 +366,7 @@ namespace AppInstaller::Repository::Microsoft auto indexResults = m_index.Search(request); SearchResult result; - std::shared_ptr<SQLiteIndexSource> sharedThis = const_cast<SQLiteIndexSource*>(this)->shared_from_this(); + std::shared_ptr<SQLiteIndexSource> sharedThis = NonConstSharedFromThis(); for (auto& indexResult : indexResults.Matches) { std::unique_ptr<IPackage> package; @@ -392,8 +391,13 @@ namespace AppInstaller::Repository::Microsoft return (other && GetIdentifier() == other->GetIdentifier()); } - SQLiteIndexWriteableSource::SQLiteIndexWriteableSource(const SourceDetails& details, std::string identifier, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock, bool isInstalledSource) : - SQLiteIndexSource(details, identifier, std::move(index), std::move(lock), isInstalledSource) + std::shared_ptr<SQLiteIndexSource> SQLiteIndexSource::NonConstSharedFromThis() const + { + return const_cast<SQLiteIndexSource*>(this)->shared_from_this(); + } + + SQLiteIndexWriteableSource::SQLiteIndexWriteableSource(const SourceDetails& details, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock, bool isInstalledSource) : + SQLiteIndexSource(details, std::move(index), std::move(lock), isInstalledSource) { } diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h @@ -2,7 +2,7 @@ // Licensed under the MIT License. #pragma once #include "Microsoft/SQLiteIndex.h" -#include "Public/AppInstallerRepositorySource.h" +#include "ISource.h" #include <AppInstallerSynchronization.h> #include <memory> @@ -13,7 +13,11 @@ namespace AppInstaller::Repository::Microsoft // A source that holds a SQLiteIndex and lock. struct SQLiteIndexSource : public std::enable_shared_from_this<SQLiteIndexSource>, public ISource { - SQLiteIndexSource(const SourceDetails& details, std::string identifier, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock = {}, bool isInstalledSource = false); + SQLiteIndexSource( + const SourceDetails& details, + SQLiteIndex&& index, + Synchronization::CrossProcessReaderWriteLock&& lock = {}, + bool isInstalledSource = false); SQLiteIndexSource(const SQLiteIndexSource&) = delete; SQLiteIndexSource& operator=(const SQLiteIndexSource&) = delete; @@ -42,9 +46,12 @@ namespace AppInstaller::Repository::Microsoft bool IsSame(const SQLiteIndexSource* other) const; private: + std::shared_ptr<SQLiteIndexSource> NonConstSharedFromThis() const; + SourceDetails m_details; Synchronization::CrossProcessReaderWriteLock m_lock; bool m_isInstalled; + protected: SQLiteIndex m_index; }; @@ -52,7 +59,11 @@ namespace AppInstaller::Repository::Microsoft // A source that holds a SQLiteIndex and lock. struct SQLiteIndexWriteableSource : public SQLiteIndexSource, public IMutablePackageSource { - SQLiteIndexWriteableSource(const SourceDetails& details, std::string identifier, SQLiteIndex&& index, Synchronization::CrossProcessReaderWriteLock&& lock = {}, bool isInstalledSource = false); + SQLiteIndexWriteableSource( + const SourceDetails& details, + SQLiteIndex&& index, + Synchronization::CrossProcessReaderWriteLock&& lock = {}, + bool isInstalledSource = false); // Adds a package version to the source. void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable.h @@ -4,7 +4,7 @@ #include "SQLiteWrapper.h" #include "SQLiteTempTable.h" #include "Microsoft/Schema/ISQLiteIndex.h" -#include "AppInstallerRepositorySearch.h" +#include "Public/winget/RepositorySearch.h" #include <optional> #include <utility> diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp @@ -96,7 +96,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 if (bindIndex.empty()) { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << PackageMatchFieldToString(filter.Field)); + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); return; } @@ -161,7 +161,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 if (bindIndex.empty()) { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << PackageMatchFieldToString(filter.Field)); + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); return; } @@ -293,7 +293,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 // TODO: Implement these more complex match types if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) { - AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << MatchTypeToString(filter.Type)); + AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); return; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h @@ -3,7 +3,7 @@ #pragma once #include "SQLiteWrapper.h" #include "Microsoft/Schema/ISQLiteIndex.h" -#include "AppInstallerRepositorySearch.h" +#include "Public/winget/RepositorySearch.h" #include <string> #include <string_view> diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h @@ -3,7 +3,7 @@ #pragma once #include "SQLiteWrapper.h" #include "Microsoft/Schema/Version.h" -#include "Public/AppInstallerRepositorySearch.h" +#include "ISource.h" #include <AppInstallerVersions.h> #include <winget/Manifest.h> #include <winget/NameNormalization.h> diff --git a/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp b/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp @@ -41,11 +41,11 @@ namespace AppInstaller::Repository PackageTrackingCatalog& PackageTrackingCatalog::operator=(PackageTrackingCatalog&&) noexcept = default; PackageTrackingCatalog::~PackageTrackingCatalog() = default; - PackageTrackingCatalog PackageTrackingCatalog::CreateForSource(const std::shared_ptr<const ISource>& source) + PackageTrackingCatalog PackageTrackingCatalog::CreateForSource(const Source& source) { // Not a valid source for tracking - const std::string& sourceIdentifier = source->GetIdentifier(); - if (sourceIdentifier.empty() || !ContainsAvailablePackages(source->GetDetails().Origin)) + const std::string sourceIdentifier = source.GetIdentifier(); + if (sourceIdentifier.empty() || !source.ContainsAvailablePackages()) { THROW_HR(E_INVALIDARG); } @@ -79,13 +79,14 @@ namespace AppInstaller::Repository // Create fake details for the source while stashing some information that might be helpful for debugging SourceDetails details; - details.Name = "Tracking for "s + source->GetDetails().Name; + details.Identifier = "*Tracking"; + details.Name = "Tracking for "s + source.GetDetails().Name; details.Origin = SourceOrigin::PackageTracking; details.Arg = pathName; PackageTrackingCatalog result; result.m_implementation = std::make_shared<PackageTrackingCatalog::implementation>(); - result.m_implementation->Source = std::make_shared<SQLiteIndexSource>(details, "*Tracking", std::move(index), std::move(lock)); + result.m_implementation->Source = std::make_shared<SQLiteIndexSource>(details, std::move(index), std::move(lock)); return result; } diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h @@ -1,310 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include <AppInstallerErrors.h> -#include <AppInstallerStrings.h> -#include <AppInstallerVersions.h> -#include <winget/LocIndependent.h> -#include <winget/Manifest.h> - -#include <map> -#include <memory> -#include <optional> -#include <string> -#include <string_view> -#include <vector> - - -namespace AppInstaller::Repository -{ - struct ISource; - - // The type of matching to perform during a search. - // The values must be declared in order of preference in search results. - enum class MatchType - { - Exact = 0, - CaseInsensitive, - StartsWith, - Fuzzy, - Substring, - FuzzySubstring, - Wildcard, - }; - - // The field to match on. - // The values must be declared in order of preference in search results. - enum class PackageMatchField - { - Id = 0, - Name, - Moniker, - Command, - Tag, - PackageFamilyName, - ProductCode, - NormalizedNameAndPublisher, - Market, - Unknown = 9999 - }; - - // A single match to be performed during a search. - struct RequestMatch - { - MatchType Type; - Utility::NormalizedString Value; - std::optional<Utility::NormalizedString> Additional; - - RequestMatch(MatchType t) : Type(t) {} - RequestMatch(MatchType t, Utility::NormalizedString& v) : Type(t), Value(v) {} - RequestMatch(MatchType t, const Utility::NormalizedString& v) : Type(t), Value(v) {} - RequestMatch(MatchType t, Utility::NormalizedString&& v) : Type(t), Value(std::move(v)) {} - RequestMatch(MatchType t, std::string_view v1, std::string_view v2) : Type(t), Value(v1), Additional(Utility::NormalizedString{ v2 }) {} - }; - - // A match on a specific field to be performed during a search. - struct PackageMatchFilter : public RequestMatch - { - PackageMatchField Field; - - PackageMatchFilter(PackageMatchField f, MatchType t) : RequestMatch(t), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, const Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString&& v) : RequestMatch(t, std::move(v)), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, std::string_view v1, std::string_view v2) : RequestMatch(t, v1, v2), Field(f) { EnsureRequiredValues(); } - - protected: - void EnsureRequiredValues() - { - // Ensure that the second value always exists when it should - if (Field == PackageMatchField::NormalizedNameAndPublisher && !Additional) - { - Additional = Utility::NormalizedString{}; - } - } - }; - - // Container for data used to filter the available manifests in a source. - // It can be thought of as: - // (Query || Inclusions...) && Filters... - // If Query and Inclusions are both empty, the starting data set will be the entire database. - // Everything && Filters... - struct SearchRequest - { - // The generic query matches against a source defined set of fields. - std::optional<RequestMatch> Query; - - // Specific fields used to include more data. - // If Query is defined, this can add more rows afterward. - // If Query is not defined, this is the only set of data included. - std::vector<PackageMatchFilter> Inclusions; - - // Specific fields used to filter the data further. - std::vector<PackageMatchFilter> Filters; - - // The maximum number of results to return. - // The default of 0 will place no limit. - size_t MaximumResults{}; - - // Returns a value indicating whether this request is for all available data. - bool IsForEverything() const; - - // Returns a string summarizing the search request. - std::string ToString() const; - }; - - // A property of a package version. - enum class PackageVersionProperty - { - Id, - Name, - SourceIdentifier, - SourceName, - Version, - Channel, - RelativePath, - // Returned in hexadecimal format - ManifestSHA256Hash, - }; - - // A property of a package version that can have multiple values. - enum class PackageVersionMultiProperty - { - // The package family names (PFN) associated with the package version - PackageFamilyName, - // The product codes associated with the package version. - ProductCode, - // TODO: Fully implement these 3; the data is not yet in the index source (name and publisher are hacks and locale is not present) - // The package names for the version; these must match in number and order with both Publisher and Locale. - Name, - // The publisher values for the version; these must match in number and order with both Name and Locale. - Publisher, - // The locale of the matching Name and Publisher values; these must match in number and order with both Name and Publisher. - // May be empty if there is only a single value for Name and Publisher. - Locale, - }; - - // A metadata item of a package version. - enum class PackageVersionMetadata : int32_t - { - // The InstallerType of an installed package - InstalledType, - // The Scope of an installed package - InstalledScope, - // The system path where the package is installed - InstalledLocation, - // The standard uninstall command; which may be interactive - StandardUninstallCommand, - // An uninstall command that should be non-interactive - SilentUninstallCommand, - // The publisher of the package - Publisher, - // The locale of the package - InstalledLocale, - // The write time for the given version - TrackingWriteTime, - }; - - // Convert a PackageVersionMetadata to a string. - std::string_view ToString(PackageVersionMetadata pvm); - - // A single package version. - struct IPackageVersion - { - using Metadata = std::map<PackageVersionMetadata, std::string>; - - virtual ~IPackageVersion() = default; - - // Gets a property of this package version. - virtual Utility::LocIndString GetProperty(PackageVersionProperty property) const = 0; - - // Gets a property of this package version that can have multiple values. - virtual std::vector<Utility::LocIndString> GetMultiProperty(PackageVersionMultiProperty property) const = 0; - - // Gets the manifest of this package version. - virtual Manifest::Manifest GetManifest() = 0; - - // Gets the source where this package version is from. - virtual std::shared_ptr<const ISource> GetSource() const = 0; - - // Gets any metadata associated with this package version. - // Primarily stores data on installed packages. - virtual Metadata GetMetadata() const = 0; - }; - - // A key to identify a package version within a package. - struct PackageVersionKey - { - PackageVersionKey() = default; - - PackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel) : - SourceId(std::move(sourceId)), Version(std::move(version)), Channel(std::move(channel)) {} - - // The source id that this version came from. - std::string SourceId; - - // The version. - Utility::NormalizedString Version; - - // The channel. - Utility::NormalizedString Channel; - }; - - // A property of a package. - enum class PackageProperty - { - Id, - Name, - }; - - // A package, potentially containing information about it's local state and the available versions. - struct IPackage - { - virtual ~IPackage() = default; - - // Gets a property of this package. - virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; - - // Gets the installed package information. - virtual std::shared_ptr<IPackageVersion> GetInstalledVersion() const = 0; - - // Gets all available versions of this package. - // The versions will be returned in sorted, descending order. - // Ex. { 4, 3, 2, 1 } - virtual std::vector<PackageVersionKey> GetAvailableVersionKeys() const = 0; - - // Gets a specific version of this package. - virtual std::shared_ptr<IPackageVersion> GetLatestAvailableVersion() const = 0; - - // Gets a specific version of this package. - virtual std::shared_ptr<IPackageVersion> GetAvailableVersion(const PackageVersionKey& versionKey) const = 0; - - // Gets a value indicating whether an available version is newer than the installed version. - virtual bool IsUpdateAvailable() const = 0; - - // Determines if the given IPackage refers to the same package as this one. - virtual bool IsSame(const IPackage*) const = 0; - }; - - // A single result from the search. - struct ResultMatch - { - // The package found by the search request. - std::shared_ptr<IPackage> Package; - - // The highest order field on which the package matched the search. - PackageMatchFilter MatchCriteria; - - ResultMatch(std::shared_ptr<IPackage> p, PackageMatchFilter f) : Package(std::move(p)), MatchCriteria(std::move(f)) {} - }; - - // Search result data. - struct SearchResult - { - // Contains a failure from the Search. - struct Failure - { - std::shared_ptr<const ISource> Source; - std::exception_ptr Exception; - }; - - // The full set of results from the search. - std::vector<ResultMatch> Matches; - - // If true, the results were truncated by the given SearchRequest::MaximumResults. - bool Truncated = false; - - // Present if the Search was against a composite source and one failed, but not limited to that scenario. - std::vector<Failure> Failures; - }; - - struct UnsupportedRequestException : public wil::ResultException - { - UnsupportedRequestException() : wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST) {} - - UnsupportedRequestException( - std::vector<std::string> unsupportedPackageMatchFields, - std::vector<std::string> requiredPackageMatchFields, - std::vector<std::string> unsupportedQueryParameters, - std::vector<std::string> requiredQueryParameters) : - wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST), - UnsupportedPackageMatchFields(std::move(unsupportedPackageMatchFields)), RequiredPackageMatchFields(std::move(requiredPackageMatchFields)), - UnsupportedQueryParameters(std::move(unsupportedQueryParameters)), RequiredQueryParameters(std::move(requiredQueryParameters)) {} - - std::vector<std::string> UnsupportedPackageMatchFields; - std::vector<std::string> RequiredPackageMatchFields; - std::vector<std::string> UnsupportedQueryParameters; - std::vector<std::string> RequiredQueryParameters; - - const char* what() const noexcept override; - - private: - mutable std::string m_whatMessage; - }; - - std::string_view MatchTypeToString(MatchType type); - - std::string_view PackageMatchFieldToString(PackageMatchField matchField); - - PackageMatchField StringToPackageMatchField(std::string_view field); -} diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySource.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySource.h @@ -1,260 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include <AppInstallerRepositorySearch.h> -#include <AppInstallerProgress.h> - -#include <chrono> -#include <filesystem> -#include <memory> -#include <optional> -#include <string> -#include <string_view> -#include <vector> - - -namespace AppInstaller::Repository -{ - // Defines the origin of the source details. - enum class SourceOrigin - { - Default, - User, - Predefined, - GroupPolicy, - Metadata, - PackageTracking, - }; - - // Defines the trust level of the source. - enum class SourceTrustLevel : uint32_t - { - None = 0x00000000, - Trusted = 0x00000001, - StoreOrigin = 0x00000002, - }; - - DEFINE_ENUM_FLAG_OPERATORS(SourceTrustLevel); - - std::string_view ToString(SourceOrigin origin); - - // Individual source agreement entry. Label will be highlighted in the display as the key of the agreement entry. - struct SourceAgreement - { - std::string Label; - std::string Text; - std::string Url; - - SourceAgreement(std::string label, std::string text, std::string url) : - Label(std::move(label)), Text(std::move(text)), Url(std::move(url)) {} - }; - - struct SourceInformation - { - // Identifier of the source agreements. This is used to identify if source agreements have changed. - std::string SourceAgreementsIdentifier; - - // List of source agreements that require user to accept. - std::vector<SourceAgreement> SourceAgreements; - - // Unsupported match fields in search request. If this field is in the filters, the request may fail. - std::vector<std::string> UnsupportedPackageMatchFields; - - // Required match fields in search request. If this field is not found in the filters, the request may fail(except Market). - std::vector<std::string> RequiredPackageMatchFields; - - // Unsupported query parameters in get manifest request. - std::vector<std::string> UnsupportedQueryParameters; - - // Required query parameters in get manifest request. - std::vector<std::string> RequiredQueryParameters; - }; - - // Interface for retrieving information about a source without acting on it. - struct SourceDetails - { - // The name of the source. - std::string Name; - - // The type of the source. - std::string Type; - - // The argument used when adding the source. - std::string Arg; - - // The source's extra data string. - std::string Data; - - // The source's unique identifier. - std::string Identifier; - - // The last time that this source was updated. - std::chrono::system_clock::time_point LastUpdateTime = {}; - - // The origin of the source. - SourceOrigin Origin = SourceOrigin::Default; - - // The trust level of the source - SourceTrustLevel TrustLevel = SourceTrustLevel::None; - - // Custom header for Rest sources - std::optional<std::string> CustomHeader; - - // Source information containing source agreements, required/unsupported match fields. - SourceInformation Information; - - // Support correlation against this source if true. - bool SupportInstalledSearchCorrelation = true; - }; - - // Fields that require user agreements. - enum class ImplicitAgreementFieldEnum : int - { - None = 0x0, - Market = 0x1, - }; - - DEFINE_ENUM_FLAG_OPERATORS(ImplicitAgreementFieldEnum); - - ImplicitAgreementFieldEnum GetAgreementFieldsFromSourceInformation(const SourceInformation& info); - - // Interface for interacting with a source from outside of the repository lib. - struct ISource - { - virtual ~ISource() = default; - - // Get the source's details. - virtual const SourceDetails& GetDetails() const = 0; - - // Gets the source's identifier; a unique identifier independent of the name - // that will not change between a remove/add or between additional adds. - // Must be suitable for filesystem names unless the source is internal to winget, - // in which case the identifier should begin with a '*' character. - virtual const std::string& GetIdentifier() const = 0; - - // Gets a value indicating whether this source is a composite of other sources, - // and thus the packages may come from disparate sources as well. - virtual bool IsComposite() const { return false; } - - // Gets the available sources if the source is composite. - virtual std::vector<std::shared_ptr<ISource>> GetAvailableSources() const { return {}; } - - // Execute a search on the source. - virtual SearchResult Search(const SearchRequest& request) const = 0; - }; - - // Interface extension to ISource for databases that can be updated after creation, like InstallingPackages - struct IMutablePackageSource - { - virtual ~IMutablePackageSource() = default; - - // Adds a package version to the source. - virtual void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; - - // Removes a package version from the source. - virtual void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; - }; - - // Gets the details for all sources. - std::vector<SourceDetails> GetSources(); - - // Gets the details for a single source. - std::optional<SourceDetails> GetSource(std::string_view name); - - // Adds a new source for the user. - bool AddSource(SourceDetails& sourceDetails, IProgressCallback& progress); - - struct OpenSourceResult - { - // The ISource returned by OpenSource - std::shared_ptr<ISource> Source; - - // List of SourceDetails that failed to update - std::vector<SourceDetails> SourcesWithUpdateFailure; - }; - - // Opens an existing source. - // Passing an empty string as the name of the source will return a source that aggregates all others. - OpenSourceResult OpenSource(std::string_view name, IProgressCallback& progress); - - // Opens an existing source. - OpenSourceResult OpenSourceFromDetails(SourceDetails& details, IProgressCallback& progress); - - // A predefined source. - // These sources are not under the direct control of the user, such as packages installed on the system. - enum class PredefinedSource - { - Installed, - ARP, - MSIX, - Installing, - }; - - // A well known source. - // These come with the app and can be disabled but not removed. - enum class WellKnownSource - { - WinGet, - MicrosoftStore, - DesktopFrameworks, - }; - - SourceDetails GetPredefinedSourceDetails(PredefinedSource source); - SourceDetails GetWellKnownSourceDetails(WellKnownSource source); - - // Opens a predefined source. - // These sources are not under the direct control of the user, such as packages installed on the system. - std::shared_ptr<ISource> OpenPredefinedSource(PredefinedSource source, IProgressCallback& progress); - - // Search behavior for composite sources. - // Only relevant for composite sources with an installed source, not for aggregates of multiple available sources. - // Installed and available packages in the result are always correlated when possible. - enum class CompositeSearchBehavior - { - // Search only installed packages. - Installed, - // Search both installed and available packages. - AllPackages, - // Search only available packages. - AvailablePackages, - }; - - // Creates a source that merges the installed packages with the given available packages. - // The source can search for installed packages only, or also include non-installed available packages. - std::shared_ptr<ISource> CreateCompositeSource( - const std::shared_ptr<ISource>& installedSource, - const std::shared_ptr<ISource>& availableSource, - CompositeSearchBehavior searchBehavior = CompositeSearchBehavior::Installed); - - // Creates a source that merges the installed packages with the given available packages from multiple sources. - // The source can search for installed packages only, or also include non-installed available packages. - std::shared_ptr<ISource> CreateCompositeSource( - const std::shared_ptr<ISource>& installedSource, - const std::vector<std::shared_ptr<ISource>>& availableSources, - CompositeSearchBehavior searchBehavior = CompositeSearchBehavior::Installed); - - // Updates an existing source. - // Return value indicates whether the named source was found. - bool UpdateSource(std::string_view name, IProgressCallback& progress); - - // Removes an existing source. - // Return value indicates whether the named source was found. - bool RemoveSource(std::string_view name, IProgressCallback& progress); - - // Drops an existing source, with no attempt to clean up its data. - // Return value indicates whether the named source was found. - // Passing an empty string drops all sources. - bool DropSource(std::string_view name); - - // Checks if a source supports passing custom header. - bool SupportsCustomHeader(const SourceDetails& sourceDetails); - - // Checks the source agreements and returns if agreements are satisfied. - bool CheckSourceAgreements(const SourceDetails& source); - - // Saves the accepted source agreements in metadata. - void SaveAcceptedSourceAgreements(const SourceDetails& source); - - // Returns true if the origin type can contain available packages. - bool ContainsAvailablePackages(SourceOrigin origin); -} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h b/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include <AppInstallerRepositorySearch.h> -#include <AppInstallerRepositorySource.h> +#include <Public/winget/RepositorySource.h> #include <memory> @@ -21,7 +20,7 @@ namespace AppInstaller::Repository // Creates or opens the tracking catalog for the given source. // TODO: Make creation exclusive to the refactored Source type. - static PackageTrackingCatalog CreateForSource(const std::shared_ptr<const ISource>& source); + static PackageTrackingCatalog CreateForSource(const Source& source); // Removes the package tracking catalog for a given source. static void RemoveForSource(const std::string& identifier); diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -0,0 +1,313 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <AppInstallerErrors.h> +#include <AppInstallerStrings.h> +#include <AppInstallerVersions.h> +#include <winget/LocIndependent.h> +#include <winget/Manifest.h> + +#include <map> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <vector> + + +namespace AppInstaller::Repository +{ + struct Source; + + // The type of matching to perform during a search. + // The values must be declared in order of preference in search results. + enum class MatchType + { + Exact = 0, + CaseInsensitive, + StartsWith, + Fuzzy, + Substring, + FuzzySubstring, + Wildcard, + }; + + // Convert a MatchType to a string. + std::string_view ToString(MatchType type); + + // The field to match on. + // The values must be declared in order of preference in search results. + enum class PackageMatchField + { + Id = 0, + Name, + Moniker, + Command, + Tag, + PackageFamilyName, + ProductCode, + NormalizedNameAndPublisher, + Market, + Unknown = 9999 + }; + + // Convert a PackageMatchField to a string. + std::string_view ToString(PackageMatchField matchField); + + // Parse a string to PackageMatchField. + PackageMatchField StringToPackageMatchField(std::string_view field); + + // A single match to be performed during a search. + struct RequestMatch + { + MatchType Type; + Utility::NormalizedString Value; + std::optional<Utility::NormalizedString> Additional; + + RequestMatch(MatchType t) : Type(t) {} + RequestMatch(MatchType t, Utility::NormalizedString& v) : Type(t), Value(v) {} + RequestMatch(MatchType t, const Utility::NormalizedString& v) : Type(t), Value(v) {} + RequestMatch(MatchType t, Utility::NormalizedString&& v) : Type(t), Value(std::move(v)) {} + RequestMatch(MatchType t, std::string_view v1, std::string_view v2) : Type(t), Value(v1), Additional(Utility::NormalizedString{ v2 }) {} + }; + + // A match on a specific field to be performed during a search. + struct PackageMatchFilter : public RequestMatch + { + PackageMatchField Field; + + PackageMatchFilter(PackageMatchField f, MatchType t) : RequestMatch(t), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, const Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString&& v) : RequestMatch(t, std::move(v)), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, std::string_view v1, std::string_view v2) : RequestMatch(t, v1, v2), Field(f) { EnsureRequiredValues(); } + + protected: + void EnsureRequiredValues() + { + // Ensure that the second value always exists when it should + if (Field == PackageMatchField::NormalizedNameAndPublisher && !Additional) + { + Additional = Utility::NormalizedString{}; + } + } + }; + + // Container for data used to filter the available manifests in a source. + // It can be thought of as: + // (Query || Inclusions...) && Filters... + // If Query and Inclusions are both empty, the starting data set will be the entire database. + // Everything && Filters... + struct SearchRequest + { + // The generic query matches against a source defined set of fields. + std::optional<RequestMatch> Query; + + // Specific fields used to include more data. + // If Query is defined, this can add more rows afterward. + // If Query is not defined, this is the only set of data included. + std::vector<PackageMatchFilter> Inclusions; + + // Specific fields used to filter the data further. + std::vector<PackageMatchFilter> Filters; + + // The maximum number of results to return. + // The default of 0 will place no limit. + size_t MaximumResults{}; + + // Returns a value indicating whether this request is for all available data. + bool IsForEverything() const; + + // Returns a string summarizing the search request. + std::string ToString() const; + }; + + // A property of a package version. + enum class PackageVersionProperty + { + Id, + Name, + SourceIdentifier, + SourceName, + Version, + Channel, + RelativePath, + // Returned in hexadecimal format + ManifestSHA256Hash, + }; + + // A property of a package version that can have multiple values. + enum class PackageVersionMultiProperty + { + // The package family names (PFN) associated with the package version + PackageFamilyName, + // The product codes associated with the package version. + ProductCode, + // TODO: Fully implement these 3; the data is not yet in the index source (name and publisher are hacks and locale is not present) + // The package names for the version; these must match in number and order with both Publisher and Locale. + Name, + // The publisher values for the version; these must match in number and order with both Name and Locale. + Publisher, + // The locale of the matching Name and Publisher values; these must match in number and order with both Name and Publisher. + // May be empty if there is only a single value for Name and Publisher. + Locale, + }; + + // A metadata item of a package version. + enum class PackageVersionMetadata : int32_t + { + // The InstallerType of an installed package + InstalledType, + // The Scope of an installed package + InstalledScope, + // The system path where the package is installed + InstalledLocation, + // The standard uninstall command; which may be interactive + StandardUninstallCommand, + // An uninstall command that should be non-interactive + SilentUninstallCommand, + // The publisher of the package + Publisher, + // The locale of the package + InstalledLocale, + // The write time for the given version + TrackingWriteTime, + }; + + // Convert a PackageVersionMetadata to a string. + std::string_view ToString(PackageVersionMetadata pvm); + + // A single package version. + struct IPackageVersion + { + using Metadata = std::map<PackageVersionMetadata, std::string>; + + virtual ~IPackageVersion() = default; + + // Gets a property of this package version. + virtual Utility::LocIndString GetProperty(PackageVersionProperty property) const = 0; + + // Gets a property of this package version that can have multiple values. + virtual std::vector<Utility::LocIndString> GetMultiProperty(PackageVersionMultiProperty property) const = 0; + + // Gets the manifest of this package version. + virtual Manifest::Manifest GetManifest() = 0; + + // Gets the source where this package version is from. + virtual Source GetSource() const = 0; + + // Gets any metadata associated with this package version. + // Primarily stores data on installed packages. + virtual Metadata GetMetadata() const = 0; + }; + + // A key to identify a package version within a package. + struct PackageVersionKey + { + PackageVersionKey() = default; + + PackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel) : + SourceId(std::move(sourceId)), Version(std::move(version)), Channel(std::move(channel)) {} + + // The source id that this version came from. + std::string SourceId; + + // The version. + Utility::NormalizedString Version; + + // The channel. + Utility::NormalizedString Channel; + }; + + // A property of a package. + enum class PackageProperty + { + Id, + Name, + }; + + // A package, potentially containing information about it's local state and the available versions. + struct IPackage + { + virtual ~IPackage() = default; + + // Gets a property of this package. + virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; + + // Gets the installed package information. + virtual std::shared_ptr<IPackageVersion> GetInstalledVersion() const = 0; + + // Gets all available versions of this package. + // The versions will be returned in sorted, descending order. + // Ex. { 4, 3, 2, 1 } + virtual std::vector<PackageVersionKey> GetAvailableVersionKeys() const = 0; + + // Gets a specific version of this package. + virtual std::shared_ptr<IPackageVersion> GetLatestAvailableVersion() const = 0; + + // Gets a specific version of this package. + virtual std::shared_ptr<IPackageVersion> GetAvailableVersion(const PackageVersionKey& versionKey) const = 0; + + // Gets a value indicating whether an available version is newer than the installed version. + virtual bool IsUpdateAvailable() const = 0; + + // Determines if the given IPackage refers to the same package as this one. + virtual bool IsSame(const IPackage*) const = 0; + }; + + // A single result from the search. + struct ResultMatch + { + // The package found by the search request. + std::shared_ptr<IPackage> Package; + + // The highest order field on which the package matched the search. + PackageMatchFilter MatchCriteria; + + ResultMatch(std::shared_ptr<IPackage> p, PackageMatchFilter f) : Package(std::move(p)), MatchCriteria(std::move(f)) {} + }; + + // Search result data. + struct SearchResult + { + // Contains a failure from the Search. + struct Failure + { + std::string SourceName; + std::exception_ptr Exception; + }; + + // The full set of results from the search. + std::vector<ResultMatch> Matches; + + // If true, the results were truncated by the given SearchRequest::MaximumResults. + bool Truncated = false; + + // Present if the Search was against a composite source and one failed, but not limited to that scenario. + std::vector<Failure> Failures; + }; + + struct UnsupportedRequestException : public wil::ResultException + { + UnsupportedRequestException() : wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST) {} + + UnsupportedRequestException( + std::vector<std::string> unsupportedPackageMatchFields, + std::vector<std::string> requiredPackageMatchFields, + std::vector<std::string> unsupportedQueryParameters, + std::vector<std::string> requiredQueryParameters) : + wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST), + UnsupportedPackageMatchFields(std::move(unsupportedPackageMatchFields)), RequiredPackageMatchFields(std::move(requiredPackageMatchFields)), + UnsupportedQueryParameters(std::move(unsupportedQueryParameters)), RequiredQueryParameters(std::move(requiredQueryParameters)) {} + + std::vector<std::string> UnsupportedPackageMatchFields; + std::vector<std::string> RequiredPackageMatchFields; + std::vector<std::string> UnsupportedQueryParameters; + std::vector<std::string> RequiredQueryParameters; + + const char* what() const noexcept override; + + private: + mutable std::string m_whatMessage; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h @@ -0,0 +1,260 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <Public/winget/RepositorySearch.h> +#include <AppInstallerProgress.h> + +#include <chrono> +#include <filesystem> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <vector> + + +namespace AppInstaller::Repository +{ + struct ISourceReference; + struct ISource; + + // Defines the origin of the source details. + enum class SourceOrigin + { + Default, + User, + Predefined, + GroupPolicy, + Metadata, + PackageTracking, + }; + + // Defines the trust level of the source. + enum class SourceTrustLevel : uint32_t + { + None = 0x00000000, + Trusted = 0x00000001, + StoreOrigin = 0x00000002, + }; + + DEFINE_ENUM_FLAG_OPERATORS(SourceTrustLevel); + + std::string_view ToString(SourceOrigin origin); + + // Fields that require user agreements. + enum class ImplicitAgreementFieldEnum : int + { + None = 0x0, + Market = 0x1, + }; + + DEFINE_ENUM_FLAG_OPERATORS(ImplicitAgreementFieldEnum); + + // A predefined source. + // These sources are not under the direct control of the user, such as packages installed on the system. + enum class PredefinedSource + { + Installed, + ARP, + MSIX, + Installing, + }; + + // A well known source. + // These come with the app and can be disabled but not removed. + enum class WellKnownSource + { + WinGet, + MicrosoftStore, + DesktopFrameworks, + }; + + // Search behavior for composite sources. + // Only relevant for composite sources with an installed source, not for aggregates of multiple available sources. + // Installed and available packages in the result are always correlated when possible. + enum class CompositeSearchBehavior + { + // Search only installed packages. + Installed, + // Search both installed and available packages. + AllPackages, + // Search only available packages. + AvailablePackages, + }; + + // Interface for source configurations. Source configurations are used to get a source reference without opening the source. + struct SourceDetails + { + // The name of the source. + std::string Name; + + // The type of the source. + std::string Type; + + // The argument used when adding the source. + std::string Arg; + + // The source's extra data string. + std::string Data; + + // The source's unique identifier. + std::string Identifier; + + // The origin of the source. + SourceOrigin Origin = SourceOrigin::Default; + + // The trust level of the source + SourceTrustLevel TrustLevel = SourceTrustLevel::None; + + // The last time that this source was updated. + std::chrono::system_clock::time_point LastUpdateTime = {}; + + // Whether the source supports InstalledSource correlation. + bool SupportInstalledSearchCorrelation = true; + }; + + // Individual source agreement entry. Label will be highlighted in the display as the key of the agreement entry. + struct SourceAgreement + { + std::string Label; + std::string Text; + std::string Url; + + SourceAgreement(std::string label, std::string text, std::string url) : + Label(std::move(label)), Text(std::move(text)), Url(std::move(url)) {} + }; + + // Interface for retrieving information about a source after opening the source. + struct SourceInformation + { + // Identifier of the source agreements. This is used to identify if source agreements have changed. + std::string SourceAgreementsIdentifier; + + // List of source agreements that require user to accept. + std::vector<SourceAgreement> SourceAgreements; + + // Unsupported match fields in search request. If this field is in the filters, the request may fail. + std::vector<std::string> UnsupportedPackageMatchFields; + + // Required match fields in search request. If this field is not found in the filters, the request may fail(except Market). + std::vector<std::string> RequiredPackageMatchFields; + + // Unsupported query parameters in get manifest request. + std::vector<std::string> UnsupportedQueryParameters; + + // Required query parameters in get manifest request. + std::vector<std::string> RequiredQueryParameters; + }; + + // Represents a source which would be interacted from outside of repository lib. + struct Source + { + // Default constructor with an empty source. + Source(); + + // Constructor to get a named source, passing empty string will get all available sources. + Source(std::string_view name); + + // Constructor to get a PredefinedSource. Like installed source, etc. + Source(PredefinedSource source); + + // Constructor to get a source coming with winget. Like winget community source, etc. + Source(WellKnownSource source); + + // Constructor for a source to be added. + Source(std::string_view name, std::string_view arg, std::string_view type); + + // Constructor for creating a composite source from a list of available sources. + Source(const std::vector<Source>& availableSources); + + // Constructor for creating a composite source from an installed source and available source(may be composite already). + Source( + const Source& installedSource, + const Source& availableSource, + CompositeSearchBehavior searchBehavior = CompositeSearchBehavior::Installed); + + // Constructor for creating a Source object from an existing ISource. + Source(std::shared_ptr<ISource> source); + + // Bool operator to check if a source reference is successfully acquired. + // Theoretically, the constructor could just throw when CreateSource returns empty. + // To avoid putting try catch everywhere, we use bool operator here. + operator bool() const; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names unless the source is internal to winget, + // in which case the identifier should begin with a '*' character. + std::string GetIdentifier() const; + + // Get the source's configuration details from settings. + SourceDetails GetDetails() const; + + // Get the source's information. + SourceInformation GetInformation() const; + + // Returns true if the origin type can contain available packages. + bool ContainsAvailablePackages() const; + + // Set custom header. + bool SetCustomHeader(std::optional<std::string> header); + + // Execute a search on the source. + SearchResult Search(const SearchRequest& request) const; + + /* Source agreements */ + + // Get required agreement fields info. + ImplicitAgreementFieldEnum GetAgreementFieldsFromSourceInformation() const; + + // Checks the source agreements and returns if agreements are satisfied. + bool CheckSourceAgreements() const; + + // Saves the accepted source agreements in metadata. + void SaveAcceptedSourceAgreements() const; + + /* Composite sources */ + + // Gets a value indicating whether this source is a composite of other sources, + // and thus the packages may come from disparate sources as well. + bool IsComposite() const; + + // Gets the available sources if the source is composite. + std::vector<Source> GetAvailableSources() const; + + /* Writable sources */ + + // Adds a package version to the source. + void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Removes a package version from the source. + void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + /* Source operations */ + + // Opens the source. This function should throw upon open failure rather than returning an empty pointer. + std::vector<SourceDetails> Open(IProgressCallback& progress); + + // Add source. Source add command. + bool Add(IProgressCallback& progress); + + // Update Source. Source update command. + std::vector<SourceDetails> Update(IProgressCallback& progress); + + // Remove source. Source remove command. + bool Remove(IProgressCallback& progress); + + // Drop source. Source reset command. + static bool DropSource(std::string_view name); + + // Get a list of all available SourceDetails. + static std::vector<SourceDetails> GetCurrentSources(); + + private: + void InitializeSourceReference(std::string_view name); + + std::vector<std::shared_ptr<ISourceReference>> m_sourceReferences; + std::shared_ptr<ISource> m_source; + bool m_isSourceToBeAdded = false; + }; +} diff --git a/src/AppInstallerRepositoryCore/RepositorySearch.cpp b/src/AppInstallerRepositoryCore/RepositorySearch.cpp @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/RepositorySearch.h" + +using namespace AppInstaller::Settings; +using namespace std::chrono_literals; + +namespace AppInstaller::Repository +{ + namespace + { + std::string GetStringVectorMessage(const std::vector<std::string>& input) + { + std::string result; + bool first = true; + for (auto const& field : input) + { + if (first) + { + result += field; + first = false; + } + else + { + result += ", " + field; + } + } + return result; + } + } + + bool SearchRequest::IsForEverything() const + { + return (!Query.has_value() && Inclusions.empty() && Filters.empty()); + } + + std::string SearchRequest::ToString() const + { + std::ostringstream result; + + result << "Query:"; + if (Query) + { + result << '\'' << Query.value().Value << "'[" << Repository::ToString(Query.value().Type) << ']'; + } + else + { + result << "[none]"; + } + + for (const auto& include : Inclusions) + { + result << " Include:" << Repository::ToString(include.Field) << "='" << include.Value << "'"; + if (include.Additional) + { + result << "+'" << include.Additional.value() << "'"; + } + result << "[" << Repository::ToString(include.Type) << "]"; + } + + for (const auto& filter : Filters) + { + result << " Filter:" << Repository::ToString(filter.Field) << "='" << filter.Value << "'[" << Repository::ToString(filter.Type) << "]"; + } + + if (MaximumResults) + { + result << " Limit:" << MaximumResults; + } + + return result.str(); + } + + std::string_view ToString(PackageVersionMetadata pvm) + { + switch (pvm) + { + case PackageVersionMetadata::InstalledType: return "InstalledType"sv; + case PackageVersionMetadata::InstalledScope: return "InstalledScope"sv; + case PackageVersionMetadata::InstalledLocation: return "InstalledLocation"sv; + case PackageVersionMetadata::StandardUninstallCommand: return "StandardUninstallCommand"sv; + case PackageVersionMetadata::SilentUninstallCommand: return "SilentUninstallCommand"sv; + case PackageVersionMetadata::Publisher: return "Publisher"sv; + case PackageVersionMetadata::InstalledLocale: return "InstalledLocale"sv; + case PackageVersionMetadata::TrackingWriteTime: return "TrackingWriteTime"sv; + default: return "Unknown"sv; + } + } + + const char* UnsupportedRequestException::what() const noexcept + { + if (m_whatMessage.empty()) + { + m_whatMessage = "The request is not supported."; + + if (!UnsupportedPackageMatchFields.empty()) + { + m_whatMessage += "Unsupported Package Match Fields: " + GetStringVectorMessage(UnsupportedPackageMatchFields); + } + if (!RequiredPackageMatchFields.empty()) + { + m_whatMessage += "Required Package Match Fields: " + GetStringVectorMessage(RequiredPackageMatchFields); + } + if (!UnsupportedQueryParameters.empty()) + { + m_whatMessage += "Unsupported Query Parameters: " + GetStringVectorMessage(UnsupportedQueryParameters); + } + if (!RequiredQueryParameters.empty()) + { + m_whatMessage += "Required Query Parameters: " + GetStringVectorMessage(RequiredQueryParameters); + } + } + return m_whatMessage.c_str(); + } + + std::string_view ToString(MatchType type) + { + using namespace std::string_view_literals; + + switch (type) + { + case MatchType::Exact: + return "Exact"sv; + case MatchType::CaseInsensitive: + return "CaseInsensitive"sv; + case MatchType::StartsWith: + return "StartsWith"sv; + case MatchType::Substring: + return "Substring"sv; + case MatchType::Wildcard: + return "Wildcard"sv; + case MatchType::Fuzzy: + return "Fuzzy"sv; + case MatchType::FuzzySubstring: + return "FuzzySubstring"sv; + } + + return "UnknownMatchType"sv; + } + + std::string_view ToString(PackageMatchField matchField) + { + using namespace std::string_view_literals; + + switch (matchField) + { + case PackageMatchField::Command: + return "Command"sv; + case PackageMatchField::Id: + return "Id"sv; + case PackageMatchField::Moniker: + return "Moniker"sv; + case PackageMatchField::Name: + return "Name"sv; + case PackageMatchField::Tag: + return "Tag"sv; + case PackageMatchField::PackageFamilyName: + return "PackageFamilyName"sv; + case PackageMatchField::ProductCode: + return "ProductCode"sv; + case PackageMatchField::NormalizedNameAndPublisher: + return "NormalizedNameAndPublisher"sv; + case PackageMatchField::Market: + return "Market"sv; + } + + return "UnknownMatchField"sv; + } + + PackageMatchField StringToPackageMatchField(std::string_view field) + { + std::string toLower = Utility::ToLower(field); + + if (toLower == "command") + { + return PackageMatchField::Command; + } + else if (toLower == "id") + { + return PackageMatchField::Id; + } + else if (toLower == "moniker") + { + return PackageMatchField::Moniker; + } + else if (toLower == "name") + { + return PackageMatchField::Name; + } + else if (toLower == "tag") + { + return PackageMatchField::Tag; + } + else if (toLower == "packagefamilyname") + { + return PackageMatchField::PackageFamilyName; + } + else if (toLower == "productcode") + { + return PackageMatchField::ProductCode; + } + else if (toLower == "normalizednameandpublisher") + { + return PackageMatchField::NormalizedNameAndPublisher; + } + else if (toLower == "market") + { + return PackageMatchField::Market; + } + + return PackageMatchField::Unknown; + } +} diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "Public/AppInstallerRepositorySource.h" - +#include "ISource.h" #include "CompositeSource.h" #include "SourceFactory.h" #include "SourceList.h" @@ -69,9 +68,9 @@ namespace AppInstaller::Repository THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE); } - std::shared_ptr<ISource> CreateSourceFromDetails(const SourceDetails& details, IProgressCallback& progress) + std::shared_ptr<ISourceReference> CreateSourceFromDetails(const SourceDetails& details) { - return GetFactoryForType(details.Type)->Create(details, progress); + return GetFactoryForType(details.Type)->Create(details); } template <typename MemberFunc> @@ -126,9 +125,19 @@ namespace AppInstaller::Repository return factory->Remove(details, progress); } + bool ContainsAvailablePackagesInternal(SourceOrigin origin) + { + return (origin == SourceOrigin::Default || origin == SourceOrigin::GroupPolicy || origin == SourceOrigin::User); + } + // Determines whether (and logs why) a source should be updated before it is opened. bool ShouldUpdateBeforeOpen(const SourceDetails& details) { + if (!ContainsAvailablePackagesInternal(details.Origin)) + { + return false; + } + constexpr static auto s_ZeroMins = 0min; auto autoUpdateTime = User().Get<Setting::AutoUpdateTimeInMinutes>(); @@ -149,23 +158,34 @@ namespace AppInstaller::Repository return false; } - std::string GetStringVectorMessage(const std::vector<std::string>& input) + SourceDetails GetPredefinedSourceDetails(PredefinedSource source) { - std::string result; - bool first = true; - for (auto const& field : input) + SourceDetails details; + details.Origin = SourceOrigin::Predefined; + + switch (source) { - if (first) - { - result += field; - first = false; - } - else - { - result += ", " + field; - } + case PredefinedSource::Installed: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::None); + return details; + case PredefinedSource::ARP: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::ARP); + return details; + case PredefinedSource::MSIX: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX); + return details; + case PredefinedSource::Installing: + details.Type = Microsoft::PredefinedWriteableSourceFactory::Type(); + // As long as there is only one type this is not particularly needed, but Arg is exposed publicly + // so this is used here for consistency with other predefined sources. + details.Arg = Microsoft::PredefinedWriteableSourceFactory::TypeToString(Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing); + return details; } - return result; + + THROW_HR(E_UNEXPECTED); } // Carries the exception from an OpenSource call and presents it back at search time. @@ -181,7 +201,7 @@ namespace AppInstaller::Repository SearchResult Search(const SearchRequest&) const override { SearchResult result; - result.Failures.emplace_back(SearchResult::Failure{ shared_from_this(), m_exception }); + result.Failures.emplace_back(SearchResult::Failure{ GetDetails().Name, m_exception }); return result; } @@ -199,6 +219,8 @@ namespace AppInstaller::Repository return "Default"sv; case SourceOrigin::User: return "User"sv; + case SourceOrigin::Predefined: + return "Predefined"sv; case SourceOrigin::GroupPolicy: return "GroupPolicy"sv; case SourceOrigin::Metadata: @@ -208,89 +230,73 @@ namespace AppInstaller::Repository } } - ImplicitAgreementFieldEnum GetAgreementFieldsFromSourceInformation(const SourceInformation& info) - { - ImplicitAgreementFieldEnum result = ImplicitAgreementFieldEnum::None; - - if (info.RequiredPackageMatchFields.end() != std::find_if(info.RequiredPackageMatchFields.begin(), info.RequiredPackageMatchFields.end(), [&](const auto& field) { return Utility::CaseInsensitiveEquals(field, "market"); }) || - info.RequiredQueryParameters.end() != std::find_if(info.RequiredQueryParameters.begin(), info.RequiredQueryParameters.end(), [&](const auto& param) { return Utility::CaseInsensitiveEquals(param, "market"); })) - { - WI_SetFlag(result, ImplicitAgreementFieldEnum::Market); - } + Source::Source() {} - return result; + Source::Source(std::string_view name) + { + InitializeSourceReference(name); } - std::vector<SourceDetails> GetSources() + Source::Source(PredefinedSource source) { - SourceList sourceList; + SourceDetails details = GetPredefinedSourceDetails(source); + m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); + } - std::vector<SourceDetails> result; - for (auto&& source : sourceList.GetCurrentSourceRefs()) - { - result.emplace_back(std::move(source)); - } + Source::Source(WellKnownSource source) + { + SourceDetails details = GetWellKnownSourceDetailsInternal(source); + m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); + } - return result; + Source::Source(std::string_view name, std::string_view arg, std::string_view type) + { + m_isSourceToBeAdded = true; + SourceDetails details; + details.Name = name; + details.Arg = arg; + details.Type = type; + m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); } - std::optional<SourceDetails> GetSource(std::string_view name) + Source::Source(const std::vector<Source>& availableSources) { - // Check all sources for the given name. - SourceList sourceList; + std::shared_ptr<CompositeSource> compositeSource = std::make_shared<CompositeSource>("*CompositeSource"); - auto source = sourceList.GetCurrentSource(name); - if (!source) - { - return {}; - } - else + for (const auto& availableSource : availableSources) { - return *source; + THROW_HR_IF(E_INVALIDARG, !availableSource.m_source || availableSource.IsComposite()); + compositeSource->AddAvailableSource(availableSource.m_source); } + + m_source = compositeSource; } - bool AddSource(SourceDetails& sourceDetails, IProgressCallback& progress) + Source::Source(const Source& installedSource, const Source& availableSource, CompositeSearchBehavior searchBehavior) { - THROW_HR_IF(E_INVALIDARG, sourceDetails.Name.empty()); - - AICLI_LOG(Repo, Info, << "Adding source: Name[" << sourceDetails.Name << "], Type[" << sourceDetails.Type << "], Arg[" << sourceDetails.Arg << "]"); - - // Check all sources for the given name. - SourceList sourceList; + THROW_HR_IF(E_INVALIDARG, !installedSource.m_source || installedSource.m_source->IsComposite() || !availableSource.m_source); - auto source = sourceList.GetCurrentSource(sourceDetails.Name); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, source != nullptr); + std::shared_ptr<CompositeSource> compositeSource = std::dynamic_pointer_cast<CompositeSource>(availableSource.m_source); - // Check for a hidden source data that we don't want to collide. - // TODO: Refactor the source interface so that we don't do this - auto hiddenSource = GetSource(sourceDetails.Name); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, - hiddenSource && hiddenSource->Origin != SourceOrigin::User && hiddenSource->Origin != SourceOrigin::Metadata); - - // Check sources allowed by group policy - auto blockingPolicy = GetPolicyBlockingUserSource(sourceDetails.Name, sourceDetails.Type, sourceDetails.Arg, false); - if (blockingPolicy != TogglePolicy::Policy::None) + if (!compositeSource) { - throw GroupPolicyException(blockingPolicy); + compositeSource = std::make_shared<CompositeSource>("*CompositeSource"); + compositeSource->AddAvailableSource(availableSource.m_source); } - sourceDetails.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(0); - sourceDetails.Origin = SourceOrigin::User; + compositeSource->SetInstalledSource(installedSource.m_source, searchBehavior); - bool result = AddSourceFromDetails(sourceDetails, progress); - if (result) - { - AICLI_LOG(Repo, Info, << "Source created with extra data: " << sourceDetails.Data); - AICLI_LOG(Repo, Info, << "Source created with identifier: " << sourceDetails.Identifier); + m_source = compositeSource; + } - sourceList.AddSource(sourceDetails); - } + Source::Source(std::shared_ptr<ISource> source) : m_source(std::move(source)) {} - return result; + Source::operator bool() const + { + return !m_sourceReferences.empty() || m_source != nullptr; } - OpenSourceResult OpenSource(std::string_view name, IProgressCallback& progress) + void Source::InitializeSourceReference(std::string_view name) { SourceList sourceList; @@ -300,73 +306,22 @@ namespace AppInstaller::Repository if (currentSources.empty()) { AICLI_LOG(Repo, Info, << "Default source requested, but no sources configured"); - return {}; } else if (currentSources.size() == 1) { AICLI_LOG(Repo, Info, << "Default source requested, only 1 source available, using the only source: " << currentSources[0].get().Name); - return OpenSource(currentSources[0].get().Name, progress); + InitializeSourceReference(currentSources[0].get().Name); } else { - AICLI_LOG(Repo, Info, << "Default source requested, multiple sources available, creating aggregated source."); - auto aggregatedSource = std::make_shared<CompositeSource>("*DefaultSource"); - OpenSourceResult result; - std::vector<std::shared_ptr<OpenExceptionProxy>> openExceptionProxies; + AICLI_LOG(Repo, Info, << "Default source requested, multiple sources available, adding all to source references."); + //auto aggregatedSource = std::make_shared<CompositeSource>("*DefaultSource"); for (auto& source : currentSources) { - AICLI_LOG(Repo, Info, << "Adding to aggregated source: " << source.get().Name); - - if (ShouldUpdateBeforeOpen(source)) - { - try - { - // TODO: Consider adding a context callback to indicate we are doing the same action - // to avoid the progress bar fill up multiple times. - if (BackgroundUpdateSourceFromDetails(source, progress)) - { - sourceList.SaveMetadata(source); - } - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - AICLI_LOG(Repo, Warning, << "Failed to update source: " << source.get().Name); - result.SourcesWithUpdateFailure.emplace_back(source); - } - } - - std::shared_ptr<ISource> openedSource; - - try - { - openedSource = CreateSourceFromDetails(source, progress); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - AICLI_LOG(Repo, Warning, << "Failed to open source: " << source.get().Name); - openExceptionProxies.emplace_back(std::make_shared<OpenExceptionProxy>(source, std::current_exception())); - } - - if (openedSource) - { - aggregatedSource->AddAvailableSource(std::move(openedSource)); - } + AICLI_LOG(Repo, Info, << "Adding to source references " << source.get().Name); + m_sourceReferences.emplace_back(CreateSourceFromDetails(source)); } - - // If all sources failed to open, then throw an exception that is specific to this case. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, !aggregatedSource->HasAvailableSource()); - - // Place all of the proxies into the source to be searched later - for (auto& proxy : openExceptionProxies) - { - aggregatedSource->AddAvailableSource(std::move(proxy)); - } - - result.Source = aggregatedSource; - return result; } } else @@ -375,442 +330,380 @@ namespace AppInstaller::Repository if (!source) { AICLI_LOG(Repo, Info, << "Named source requested, but not found: " << name); - return {}; } else { AICLI_LOG(Repo, Info, << "Named source requested, found: " << source->Name); - OpenSourceResult result; - - if (ShouldUpdateBeforeOpen(*source)) - { - try - { - if (BackgroundUpdateSourceFromDetails(*source, progress)) - { - sourceList.SaveMetadata(*source); - } - } - catch (...) - { - AICLI_LOG(Repo, Warning, << "Failed to update source: " << (*source).Name); - result.SourcesWithUpdateFailure.emplace_back(*source); - } - } - - result.Source = CreateSourceFromDetails(*source, progress); - return result; + m_sourceReferences.emplace_back(CreateSourceFromDetails(*source)); } } } - OpenSourceResult OpenSourceFromDetails(SourceDetails& details, IProgressCallback& progress) + std::string Source::GetIdentifier() const { - OpenSourceResult result; - - // Get the details again by name from the source list because SaveMetadata only updates the LastUpdateTime - // if the details came from the same instance of the list that's being saved. - // Some sources that do not need updating like the Installed source, do not have Name values. - // Restricted sources don't have full functionality - if (!details.Name.empty()) + if (m_source) { - SourceList sourceList; - auto source = sourceList.GetSource(details.Name); - if (!source) - { - AICLI_LOG(Repo, Info, << "Named source no longer found. Source may have been removed by the user: " << details.Name); - return {}; - } + return m_source->GetIdentifier(); + } + else if (m_sourceReferences.size() == 1) + { + return m_sourceReferences[0]->GetIdentifier(); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + } - if (ShouldUpdateBeforeOpen(*source)) - { - try - { - if (BackgroundUpdateSourceFromDetails(*source, progress)) - { - sourceList.SaveMetadata(*source); - } - } - catch (...) - { - AICLI_LOG(Repo, Warning, << "Failed to update source: " << details.Name); - result.SourcesWithUpdateFailure.emplace_back(*source); - } - } + SourceDetails Source::GetDetails() const + { + if (m_source) + { + return m_source->GetDetails(); } + else if (m_sourceReferences.size() == 1) + { + return m_sourceReferences[0]->GetDetails(); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + } - result.Source = CreateSourceFromDetails(details, progress); - return result; + SourceInformation Source::GetInformation() const + { + if (m_source && !m_source->IsComposite()) + { + return m_source->GetInformation(); + } + else if (m_sourceReferences.size() == 1) + { + return m_sourceReferences[0]->GetInformation(); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } } - std::shared_ptr<ISource> OpenPredefinedSource(PredefinedSource source, IProgressCallback& progress) + bool Source::ContainsAvailablePackages() const { - SourceDetails details = GetPredefinedSourceDetails(source); - return CreateSourceFromDetails(details, progress); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), IsComposite()); + return ContainsAvailablePackagesInternal(GetDetails().Origin); } - SourceDetails GetPredefinedSourceDetails(PredefinedSource source) + bool Source::SetCustomHeader(std::optional<std::string> header) { - SourceDetails details; - details.Origin = SourceOrigin::Predefined; - - switch (source) - { - case PredefinedSource::Installed: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::None); - return details; - case PredefinedSource::ARP: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::ARP); - return details; - case PredefinedSource::MSIX: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX); - return details; - case PredefinedSource::Installing: - details.Type = Microsoft::PredefinedWriteableSourceFactory::Type(); - // As long as there is only one type this is not particularly needed, but Arg is exposed publicly - // so this is used here for consistency with other predefined sources. - details.Arg = Microsoft::PredefinedWriteableSourceFactory::TypeToString(Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing); - return details; - } - - THROW_HR(E_UNEXPECTED); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_sourceReferences.size() != 1); + return m_sourceReferences[0]->SetCustomHeader(header); } - SourceDetails GetWellKnownSourceDetails(WellKnownSource source) + SearchResult Source::Search(const SearchRequest& request) const { - return GetWellKnownSourceDetailsInternal(source); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); + return m_source->Search(request); } - std::shared_ptr<ISource> CreateCompositeSource(const std::shared_ptr<ISource>& installedSource, const std::shared_ptr<ISource>& availableSource, CompositeSearchBehavior searchBehavior) + ImplicitAgreementFieldEnum Source::GetAgreementFieldsFromSourceInformation() const { - std::shared_ptr<CompositeSource> result = std::dynamic_pointer_cast<CompositeSource>(availableSource); + ImplicitAgreementFieldEnum result = ImplicitAgreementFieldEnum::None; - if (!result) + auto info = GetInformation(); + if (info.RequiredPackageMatchFields.end() != std::find_if(info.RequiredPackageMatchFields.begin(), info.RequiredPackageMatchFields.end(), [&](const auto& field) { return Utility::CaseInsensitiveEquals(field, "market"); }) || + info.RequiredQueryParameters.end() != std::find_if(info.RequiredQueryParameters.begin(), info.RequiredQueryParameters.end(), [&](const auto& param) { return Utility::CaseInsensitiveEquals(param, "market"); })) { - result = std::make_shared<CompositeSource>("*CompositeSource"); - result->AddAvailableSource(availableSource); + WI_SetFlag(result, ImplicitAgreementFieldEnum::Market); } - result->SetInstalledSource(installedSource, searchBehavior); - return result; } - std::shared_ptr<ISource> CreateCompositeSource( - const std::shared_ptr<ISource>& installedSource, - const std::vector<std::shared_ptr<ISource>>& availableSources, - CompositeSearchBehavior searchBehavior) + bool Source::CheckSourceAgreements() const { - std::shared_ptr<CompositeSource> result = std::make_shared<CompositeSource>("*CompositeSource"); - - for (const auto& availableSource : availableSources) - { - result->AddAvailableSource(availableSource); - } + auto sourceName = GetDetails().Name; + auto agreementFields = GetAgreementFieldsFromSourceInformation(); + auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier; - if (installedSource) - { - result->SetInstalledSource(installedSource, searchBehavior); - } - - return result; + SourceList sourceList; + return sourceList.CheckSourceAgreements(sourceName, agreementsIdentifier, agreementFields); } - bool UpdateSource(std::string_view name, IProgressCallback& progress) + void Source::SaveAcceptedSourceAgreements() const { - THROW_HR_IF(E_INVALIDARG, name.empty()); + auto sourceName = GetDetails().Name; + auto agreementFields = GetAgreementFieldsFromSourceInformation(); + auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier; SourceList sourceList; + return sourceList.SaveAcceptedSourceAgreements(sourceName, agreementsIdentifier, agreementFields); + } - auto source = sourceList.GetCurrentSource(name); - if (!source) + bool Source::IsComposite() const + { + if (m_source) { - AICLI_LOG(Repo, Info, << "Named source to be updated, but not found: " << name); - return false; + return m_source->IsComposite(); + } + else if (m_sourceReferences.size() > 0) + { + return m_sourceReferences.size() > 1; } else { - AICLI_LOG(Repo, Info, << "Named source to be updated, found: " << source->Name); - - bool result = UpdateSourceFromDetails(*source, progress); - if (result) - { - sourceList.SaveMetadata(*source); - } - - return result; + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); } } - bool RemoveSource(std::string_view name, IProgressCallback& progress) + std::vector<Source> Source::GetAvailableSources() const { - THROW_HR_IF(E_INVALIDARG, name.empty()); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source || !m_source->IsComposite()); - SourceList sourceList; - - auto source = sourceList.GetCurrentSource(name); - if (!source) + std::vector<Source> result; + for (auto const& availableSource : m_source->GetAvailableSources()) { - AICLI_LOG(Repo, Info, << "Named source to be removed, but not found: " << name); - return false; + result.emplace_back(availableSource); } - else - { - AICLI_LOG(Repo, Info, << "Named source to be removed, found: " << source->Name << " [" << ToString(source->Origin) << ']'); - EnsureSourceIsRemovable(*source); + return result; + } - bool result = RemoveSourceFromDetails(*source, progress); - if (result) - { - sourceList.RemoveSource(*source); - } + void Source::AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); + auto writableSource = std::dynamic_pointer_cast<IMutablePackageSource>(m_source); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource); + writableSource->AddPackageVersion(manifest, relativePath); + } - return result; - } + void Source::RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); + auto writableSource = std::dynamic_pointer_cast<IMutablePackageSource>(m_source); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource); + writableSource->RemovePackageVersion(manifest, relativePath); } - bool DropSource(std::string_view name) + std::vector<SourceDetails> Source::Open(IProgressCallback& progress) { - if (name.empty()) - { - SourceList::RemoveSettingsStreams(); - return true; - } - else + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.empty()); + + std::vector<SourceDetails> result; + + if (!m_source) { SourceList sourceList; - auto source = sourceList.GetCurrentSource(name); - if (!source) + // Check for updates before opening. + for (auto& sourceReference : m_sourceReferences) { - AICLI_LOG(Repo, Info, << "Named source to be dropped, but not found: " << name); - return false; + auto& details = sourceReference->GetDetails(); + if (ShouldUpdateBeforeOpen(details)) + { + try + { + // TODO: Consider adding a context callback to indicate we are doing the same action + // to avoid the progress bar fill up multiple times. + if (BackgroundUpdateSourceFromDetails(details, progress)) + { + auto detailsInternal = sourceList.GetSource(details.Name); + detailsInternal->LastUpdateTime = details.LastUpdateTime; + sourceList.SaveMetadata(*detailsInternal); + } + else + { + AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); + result.emplace_back(details); + } + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Warning, << "Failed to update source: " << details.Name); + result.emplace_back(details); + } + } } - else + + if (m_sourceReferences.size() > 1) { - AICLI_LOG(Repo, Info, << "Named source to be dropped, found: " << source->Name); + AICLI_LOG(Repo, Info, << "Multiple sources available, creating aggregated source."); + auto aggregatedSource = std::make_shared<CompositeSource>("*DefaultSource"); + std::vector<std::shared_ptr<OpenExceptionProxy>> openExceptionProxies; - EnsureSourceIsRemovable(*source); - sourceList.RemoveSource(*source); + for (auto& sourceReference : m_sourceReferences) + { + AICLI_LOG(Repo, Info, << "Adding to aggregated source: " << sourceReference->GetDetails().Name); - return true; - } - } - } + try - bool SupportsCustomHeader(const SourceDetails& sourceDetails) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (Utility::CaseInsensitiveEquals(Microsoft::ConfigurableTestSourceFactory::Type(), sourceDetails.Type)) - { - return true; - } -#endif + { + aggregatedSource->AddAvailableSource(sourceReference->Open(progress)); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Warning, << "Failed to open available source: " << sourceReference->GetDetails().Name); + openExceptionProxies.emplace_back(std::make_shared<OpenExceptionProxy>(sourceReference->GetDetails(), std::current_exception())); + } + } - return Utility::CaseInsensitiveEquals(Rest::RestSourceFactory::Type(), sourceDetails.Type); - } + // If all sources failed to open, then throw an exception that is specific to this case. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, !aggregatedSource->HasAvailableSource()); - bool CheckSourceAgreements(const SourceDetails& source) - { - SourceList sourceList; - return sourceList.CheckSourceAgreements(source); - } + // Place all of the proxies into the source to be searched later + for (auto& proxy : openExceptionProxies) + { + aggregatedSource->AddAvailableSource(std::move(proxy)); + } - void SaveAcceptedSourceAgreements(const SourceDetails& source) - { - SourceList sourceList; - sourceList.SaveAcceptedSourceAgreements(source); - } + m_source = aggregatedSource; + } + else + { + m_source = m_sourceReferences[0]->Open(progress); + } + } - bool ContainsAvailablePackages(SourceOrigin origin) - { - return (origin == SourceOrigin::Default || origin == SourceOrigin::GroupPolicy || origin == SourceOrigin::User); + return result; } - bool SearchRequest::IsForEverything() const + bool Source::Add(IProgressCallback& progress) { - return (!Query.has_value() && Inclusions.empty() && Filters.empty()); - } + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_isSourceToBeAdded || m_sourceReferences.size() != 1); - std::string SearchRequest::ToString() const - { - std::ostringstream result; + auto& sourceDetails = m_sourceReferences[0]->GetDetails(); - result << "Query:"; - if (Query) - { - result << '\'' << Query.value().Value << "'[" << MatchTypeToString(Query.value().Type) << ']'; - } - else - { - result << "[none]"; - } + AICLI_LOG(Repo, Info, << "Adding source: Name[" << sourceDetails.Name << "], Type[" << sourceDetails.Type << "], Arg[" << sourceDetails.Arg << "]"); - for (const auto& include : Inclusions) - { - result << " Include:" << PackageMatchFieldToString(include.Field) << "='" << include.Value << "'"; - if (include.Additional) - { - result << "+'" << include.Additional.value() << "'"; - } - result << "[" << MatchTypeToString(include.Type) << "]"; - } + // Check all sources for the given name. + SourceList sourceList; - for (const auto& filter : Filters) + auto source = sourceList.GetSource(sourceDetails.Name); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, source != nullptr && source->Origin != SourceOrigin::Metadata && !source->IsTombstone); + + // Check sources allowed by group policy + auto blockingPolicy = GetPolicyBlockingUserSource(sourceDetails.Name, sourceDetails.Type, sourceDetails.Arg, false); + if (blockingPolicy != TogglePolicy::Policy::None) { - result << " Filter:" << PackageMatchFieldToString(filter.Field) << "='" << filter.Value << "'[" << MatchTypeToString(filter.Type) << "]"; + throw GroupPolicyException(blockingPolicy); } - if (MaximumResults) + sourceDetails.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(0); + sourceDetails.Origin = SourceOrigin::User; + + bool result = AddSourceFromDetails(sourceDetails, progress); + if (result) { - result << " Limit:" << MaximumResults; + sourceList.AddSource(sourceDetails); + SaveAcceptedSourceAgreements(); + m_isSourceToBeAdded = false; + AICLI_LOG(Repo, Info, << "Source created with extra data: " << sourceDetails.Data); } - return result.str(); + return result; } - std::string_view ToString(PackageVersionMetadata pvm) + std::vector<SourceDetails> Source::Update(IProgressCallback& progress) { - switch (pvm) - { - case PackageVersionMetadata::InstalledType: return "InstalledType"sv; - case PackageVersionMetadata::InstalledScope: return "InstalledScope"sv; - case PackageVersionMetadata::InstalledLocation: return "InstalledLocation"sv; - case PackageVersionMetadata::StandardUninstallCommand: return "StandardUninstallCommand"sv; - case PackageVersionMetadata::SilentUninstallCommand: return "SilentUninstallCommand"sv; - case PackageVersionMetadata::Publisher: return "Publisher"sv; - case PackageVersionMetadata::InstalledLocale: return "InstalledLocale"sv; - case PackageVersionMetadata::TrackingWriteTime: return "TrackingWriteTime"sv; - default: return "Unknown"sv; - } - } + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_source || m_sourceReferences.empty()); - const char* UnsupportedRequestException::what() const noexcept - { - if (m_whatMessage.empty()) + SourceList sourceList; + std::vector<SourceDetails> result; + + for (auto& sourceReference : m_sourceReferences) { - m_whatMessage = "The request is not supported."; + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !ContainsAvailablePackagesInternal(sourceReference->GetDetails().Origin)); - if (!UnsupportedPackageMatchFields.empty()) - { - m_whatMessage += "Unsupported Package Match Fields: " + GetStringVectorMessage(UnsupportedPackageMatchFields); - } - if (!RequiredPackageMatchFields.empty()) - { - m_whatMessage += "Required Package Match Fields: " + GetStringVectorMessage(RequiredPackageMatchFields); - } - if (!UnsupportedQueryParameters.empty()) + auto& details = sourceReference->GetDetails(); + AICLI_LOG(Repo, Info, << "Named source to be updated, found: " << details.Name); + + try { - m_whatMessage += "Unsupported Query Parameters: " + GetStringVectorMessage(UnsupportedQueryParameters); + // TODO: Consider adding a context callback to indicate we are doing the same action + // to avoid the progress bar fill up multiple times. + if (UpdateSourceFromDetails(details, progress)) + { + auto detailsInternal = sourceList.GetSource(details.Name); + detailsInternal->LastUpdateTime = details.LastUpdateTime; + sourceList.SaveMetadata(*detailsInternal); + } + else + { + AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); + result.emplace_back(details); + } } - if (!RequiredQueryParameters.empty()) + catch (...) { - m_whatMessage += "Required Query Parameters: " + GetStringVectorMessage(RequiredQueryParameters); + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); + result.emplace_back(details); } } - return m_whatMessage.c_str(); - } - std::string_view MatchTypeToString(MatchType type) - { - using namespace std::string_view_literals; - - switch (type) - { - case MatchType::Exact: - return "Exact"sv; - case MatchType::CaseInsensitive: - return "CaseInsensitive"sv; - case MatchType::StartsWith: - return "StartsWith"sv; - case MatchType::Substring: - return "Substring"sv; - case MatchType::Wildcard: - return "Wildcard"sv; - case MatchType::Fuzzy: - return "Fuzzy"sv; - case MatchType::FuzzySubstring: - return "FuzzySubstring"sv; - } - - return "UnknownMatchType"sv; + return result; } - std::string_view PackageMatchFieldToString(PackageMatchField matchField) + bool Source::Remove(IProgressCallback& progress) { - using namespace std::string_view_literals; - - switch (matchField) - { - case PackageMatchField::Command: - return "Command"sv; - case PackageMatchField::Id: - return "Id"sv; - case PackageMatchField::Moniker: - return "Moniker"sv; - case PackageMatchField::Name: - return "Name"sv; - case PackageMatchField::Tag: - return "Tag"sv; - case PackageMatchField::PackageFamilyName: - return "PackageFamilyName"sv; - case PackageMatchField::ProductCode: - return "ProductCode"sv; - case PackageMatchField::NormalizedNameAndPublisher: - return "NormalizedNameAndPublisher"sv; - case PackageMatchField::Market: - return "Market"sv; - } - - return "UnknownMatchField"sv; - } + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.size() != 1 || m_source); - PackageMatchField StringToPackageMatchField(std::string_view field) - { - std::string toLower = Utility::ToLower(field); + const auto& details = m_sourceReferences[0]->GetDetails(); + AICLI_LOG(Repo, Info, << "Named source to be removed, found: " << details.Name << " [" << ToString(details.Origin) << ']'); - if (toLower == "command") - { - return PackageMatchField::Command; - } - else if (toLower == "id") - { - return PackageMatchField::Id; - } - else if (toLower == "moniker") - { - return PackageMatchField::Moniker; - } - else if (toLower == "name") - { - return PackageMatchField::Name; - } - else if (toLower == "tag") - { - return PackageMatchField::Tag; - } - else if (toLower == "packagefamilyname") + EnsureSourceIsRemovable(details); + + bool result = RemoveSourceFromDetails(details, progress); + if (result) { - return PackageMatchField::PackageFamilyName; + SourceList sourceList; + sourceList.RemoveSource(details); } - else if (toLower == "productcode") + + return result; + } + + std::vector<SourceDetails> Source::GetCurrentSources() + { + SourceList sourceList; + + std::vector<SourceDetails> result; + for (auto&& source : sourceList.GetCurrentSourceRefs()) { - return PackageMatchField::ProductCode; + result.emplace_back(std::move(source)); } - else if (toLower == "normalizednameandpublisher") + + return result; + } + + bool Source::DropSource(std::string_view name) + { + if (name.empty()) { - return PackageMatchField::NormalizedNameAndPublisher; + SourceList::RemoveSettingsStreams(); + return true; } - else if (toLower == "market") + else { - return PackageMatchField::Market; - } + SourceList sourceList; + + auto source = sourceList.GetCurrentSource(name); + if (!source) + { + AICLI_LOG(Repo, Info, << "Named source to be dropped, but not found: " << name); + return false; + } + else + { + AICLI_LOG(Repo, Info, << "Named source to be dropped, found: " << source->Name); - return PackageMatchField::Unknown; + EnsureSourceIsRemovable(*source); + sourceList.RemoveSource(*source); + + return true; + } + } } #ifndef AICLI_DISABLE_TEST_HOOKS diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.h b/src/AppInstallerRepositoryCore/Rest/RestClient.h @@ -6,7 +6,7 @@ #include "Rest/Schema/IRestClient.h" #include "Rest/Schema/HttpClientHelper.h" #include "cpprest/json.h" -#include "AppInstallerRepositorySource.h" +#include "ISource.h" namespace AppInstaller::Repository::Rest { diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp @@ -286,9 +286,9 @@ namespace AppInstaller::Repository::Rest return m_versionInfo.Manifest.value(); } - std::shared_ptr<const ISource> GetSource() const override + Source GetSource() const override { - return GetReferenceSource(); + return Source{ GetReferenceSource() }; } IPackageVersion::Metadata GetMetadata() const override @@ -380,37 +380,24 @@ namespace AppInstaller::Repository::Rest } } - RestSource::RestSource(const SourceDetails& details, std::string identifier, RestClient&& restClient) - : m_details(details), m_restClient(std::move(restClient)) + RestSource::RestSource(const SourceDetails& details, SourceInformation information, RestClient&& restClient) + : m_details(details), m_information(std::move(information)), m_restClient(std::move(restClient)) { - m_details.Identifier = std::move(identifier); - - const auto& sourceInformation = m_restClient.GetSourceInformation(); - m_details.Information.UnsupportedPackageMatchFields = sourceInformation.UnsupportedPackageMatchFields; - m_details.Information.RequiredPackageMatchFields = sourceInformation.RequiredPackageMatchFields; - m_details.Information.UnsupportedQueryParameters = sourceInformation.UnsupportedQueryParameters; - m_details.Information.RequiredQueryParameters = sourceInformation.RequiredQueryParameters; - - m_details.Information.SourceAgreementsIdentifier = sourceInformation.SourceAgreementsIdentifier; - for (auto const& agreement : sourceInformation.SourceAgreements) - { - m_details.Information.SourceAgreements.emplace_back(agreement.Label, agreement.Text, agreement.Url); - } } - const SourceDetails& RestSource::GetDetails() const + const std::string& RestSource::GetIdentifier() const { - return m_details; + return m_details.Identifier; } - const RestClient& RestSource::GetRestClient() const + const SourceDetails& RestSource::GetDetails() const { - return m_restClient; + return m_details; } - const std::string& RestSource::GetIdentifier() const + SourceInformation RestSource::GetInformation() const { - return m_details.Identifier; + return m_information; } SearchResult RestSource::Search(const SearchRequest& request) const @@ -418,7 +405,7 @@ namespace AppInstaller::Repository::Rest IRestClient::SearchResult results = m_restClient.Search(request); SearchResult searchResult; - std::shared_ptr<RestSource> sharedThis = const_cast<RestSource*>(this)->shared_from_this(); + std::shared_ptr<RestSource> sharedThis = NonConstSharedFromThis(); for (auto& result : results.Matches) { std::shared_ptr<IPackage> package = std::make_shared<AvailablePackage>(sharedThis, std::move(result)); @@ -434,8 +421,18 @@ namespace AppInstaller::Repository::Rest return searchResult; } + const RestClient& RestSource::GetRestClient() const + { + return m_restClient; + } + bool RestSource::IsSame(const RestSource* other) const { return (other && GetIdentifier() == other->GetIdentifier()); } + + std::shared_ptr<RestSource> RestSource::NonConstSharedFromThis() const + { + return const_cast<RestSource*>(this)->shared_from_this(); + } } diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.h b/src/AppInstallerRepositoryCore/Rest/RestSource.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "Public/AppInstallerRepositorySource.h" +#include "ISource.h" #include "RestClient.h" namespace AppInstaller::Repository::Rest @@ -9,7 +9,7 @@ namespace AppInstaller::Repository::Rest // A source that holds a RestSource. struct RestSource : public std::enable_shared_from_this<RestSource>, public ISource { - RestSource(const SourceDetails& details, std::string identifier, RestClient&& m_restClient); + RestSource(const SourceDetails& details, SourceInformation information, RestClient&& restClient); RestSource(const RestSource&) = delete; RestSource& operator=(const RestSource&) = delete; @@ -19,25 +19,30 @@ namespace AppInstaller::Repository::Rest ~RestSource() = default; - // Get the source's details. - const SourceDetails& GetDetails() const override; - // Gets the source's identifier; a unique identifier independent of the name // that will not change between a remove/add or between additional adds. // Must be suitable for filesystem names. const std::string& GetIdentifier() const override; - // Gets the rest client. - const RestClient& GetRestClient() const; + // Get the source's details. + const SourceDetails& GetDetails() const override; + + SourceInformation GetInformation() const override; // Execute a search on the source. SearchResult Search(const SearchRequest& request) const override; + // Gets the rest client. + const RestClient& GetRestClient() const; + // Determines if the other source refers to the same as this. bool IsSame(const RestSource* other) const; private: + std::shared_ptr<RestSource> NonConstSharedFromThis() const; + SourceDetails m_details; + SourceInformation m_information; RestClient m_restClient; }; } diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp @@ -12,16 +12,76 @@ namespace AppInstaller::Repository::Rest { namespace { + struct RestSourceReference : public ISourceReference + { + RestSourceReference(const SourceDetails& details) : m_details(details) {} + + SourceDetails& GetDetails() override { return m_details; }; + + std::string GetIdentifier() override + { + Initialize(); + return m_details.Identifier; + } + + SourceInformation GetInformation() override + { + Initialize(); + return m_information; + } + + // Set custom header. Returns false if custom header is not supported. + bool SetCustomHeader(std::optional<std::string> header) override + { + m_customHeader = header; + return true; + } + + std::shared_ptr<ISource> Open(IProgressCallback&) override + { + Initialize(); + RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader); + return std::make_shared<RestSource>(m_details, m_information, std::move(restClient)); + } + + private: + void Initialize() + { + std::call_once(m_initializeFlag, + [&]() + { + RestClient restClient = RestClient::Create(m_details.Arg, m_customHeader); + + m_details.Identifier = restClient.GetSourceIdentifier(); + + const auto& sourceInformation = restClient.GetSourceInformation(); + m_information.UnsupportedPackageMatchFields = sourceInformation.UnsupportedPackageMatchFields; + m_information.RequiredPackageMatchFields = sourceInformation.RequiredPackageMatchFields; + m_information.UnsupportedQueryParameters = sourceInformation.UnsupportedQueryParameters; + m_information.RequiredQueryParameters = sourceInformation.RequiredQueryParameters; + + m_information.SourceAgreementsIdentifier = sourceInformation.SourceAgreementsIdentifier; + for (auto const& agreement : sourceInformation.SourceAgreements) + { + m_information.SourceAgreements.emplace_back(agreement.Label, agreement.Text, agreement.Url); + } + }); + } + + SourceDetails m_details; + SourceInformation m_information; + std::optional<std::string> m_customHeader; + std::once_flag m_initializeFlag; + }; + // The base class for data that comes from a rest based source. struct RestSourceFactoryImpl : public ISourceFactory { - std::shared_ptr<ISource> Create(const SourceDetails& details, IProgressCallback&) override final + std::shared_ptr<ISourceReference> Create(const SourceDetails& details) override final { THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); - RestClient restClient = RestClient::Create(details.Arg, details.CustomHeader); - - return std::make_shared<RestSource>(details, restClient.GetSourceIdentifier(), std::move(restClient)); + return std::make_shared<RestSourceReference>(details); } bool Add(SourceDetails& details, IProgressCallback&) override final @@ -39,9 +99,6 @@ namespace AppInstaller::Repository::Rest THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE, !Utility::IsUrlRemote(details.Arg)); THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, !Utility::IsUrlSecure(details.Arg)); - RestClient restClient = RestClient::Create(details.Arg, details.CustomHeader); - details.Identifier = restClient.GetSourceIdentifier(); - return true; } diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.h b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "Public/AppInstallerRepositorySource.h" +#include "ISource.h" #include "SourceFactory.h" #include <string_view> diff --git a/src/AppInstallerRepositoryCore/SourceFactory.h b/src/AppInstallerRepositoryCore/SourceFactory.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include <AppInstallerRepositorySource.h> +#include "ISource.h" #include <AppInstallerProgress.h> #include <memory> @@ -15,7 +15,7 @@ namespace AppInstaller::Repository virtual ~ISourceFactory() = default; // Creates a source object from the given details. - virtual std::shared_ptr<ISource> Create(const SourceDetails& details, IProgressCallback& progress) = 0; + virtual std::shared_ptr<ISourceReference> Create(const SourceDetails& details) = 0; // Adds the source from the given details, writing back to the details any changes. // Return value indicates whether the action completed. diff --git a/src/AppInstallerRepositoryCore/SourceList.cpp b/src/AppInstallerRepositoryCore/SourceList.cpp @@ -193,7 +193,7 @@ namespace AppInstaller::Repository bool ShouldBeHidden(const SourceDetailsInternal& details) { - return details.IsTombstone || details.Origin == SourceOrigin::Metadata; + return details.IsTombstone || details.Origin == SourceOrigin::Metadata || !details.IsVisible; } } @@ -287,11 +287,7 @@ namespace AppInstaller::Repository details.Data = s_Source_DesktopFrameworks_Data; details.Identifier = s_Source_DesktopFrameworks_Identifier; details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin; - // Cheat the system and call this a tombstone. This effectively hides it from everything outside - // of this file, while still allowing it to properly save metadata. There might be problems - // if someone chooses the exact same name as this, which is why its name is very long. - // TODO: When refactoring the source interface, handle this with Visibility or similar. - details.IsTombstone = true; + details.IsVisible = false; return details; } } @@ -352,14 +348,9 @@ namespace AppInstaller::Repository for (size_t i = 0; !sourcesSet && i < 10; ++i) { - auto currentSource = GetCurrentSource(details.Name); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, currentSource != nullptr && !DoSourceDetailsInternalMatch(details, *currentSource)); - - // Check for a hidden source data that we don't want to collide. - // TODO: Refactor the source interface so that we don't do this auto itr = FindSource(details.Name, true); THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, - itr != m_sourceList.end() && itr->Origin != SourceOrigin::User && itr->Origin != SourceOrigin::Metadata); + itr != m_sourceList.end() && itr->Origin != SourceOrigin::Metadata && !itr->IsTombstone); // Erase the source's entry if applicable if (itr != m_sourceList.end()) @@ -451,17 +442,15 @@ namespace AppInstaller::Repository SaveMetadataInternal(details); } - bool SourceList::CheckSourceAgreements(const SourceDetails& details) + bool SourceList::CheckSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields) { - auto agreementFields = GetAgreementFieldsFromSourceInformation(details.Information); - - if (agreementFields == ImplicitAgreementFieldEnum::None && details.Information.SourceAgreementsIdentifier.empty()) + if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty()) { // No agreements to be accepted. return true; } - auto detailsInternal = GetCurrentSource(details.Name); + auto detailsInternal = GetCurrentSource(sourceName); if (!detailsInternal) { // Source not found. @@ -469,20 +458,18 @@ namespace AppInstaller::Repository } return static_cast<int>(agreementFields) == detailsInternal->AcceptedAgreementFields && - details.Information.SourceAgreementsIdentifier == detailsInternal->AcceptedAgreementsIdentifier; + agreementsIdentifier == detailsInternal->AcceptedAgreementsIdentifier; } - void SourceList::SaveAcceptedSourceAgreements(const SourceDetails& details) + void SourceList::SaveAcceptedSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields) { - auto agreementFields = GetAgreementFieldsFromSourceInformation(details.Information); - - if (agreementFields == ImplicitAgreementFieldEnum::None && details.Information.SourceAgreementsIdentifier.empty()) + if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty()) { // No agreements to be accepted. return; } - auto detailsInternal = GetCurrentSource(details.Name); + auto detailsInternal = GetCurrentSource(sourceName); if (!detailsInternal) { // No source to update. @@ -490,9 +477,9 @@ namespace AppInstaller::Repository } detailsInternal->AcceptedAgreementFields = static_cast<int>(agreementFields); - detailsInternal->AcceptedAgreementsIdentifier = details.Information.SourceAgreementsIdentifier; + detailsInternal->AcceptedAgreementsIdentifier = agreementsIdentifier; - SaveMetadataInternal(details); + SaveMetadataInternal(*detailsInternal); } void SourceList::RemoveSettingsStreams() @@ -562,7 +549,7 @@ namespace AppInstaller::Repository result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::WinGet)); } - // Since we are using the tombstone trick, this is added just to have the source in the internal + // Since the source is not visible outside, this is added just to have the source in the internal // list for tracking updates. Thus there is no need to check a policy. result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::DesktopFrameworks)); } diff --git a/src/AppInstallerRepositoryCore/SourceList.h b/src/AppInstallerRepositoryCore/SourceList.h @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #pragma once -#include "AppInstallerRepositorySource.h" +#include "ISource.h" #include <winget/Settings.h> @@ -23,6 +23,11 @@ namespace AppInstaller::Repository // If true, this is a tombstone, marking the deletion of a source at a lower priority origin. bool IsTombstone = false; + + // If false, this is not visible in GetCurrentSource or GetAllSources, it's only available when explicitly requested. + bool IsVisible = true; + + // Accepted agreements info. std::string AcceptedAgreementsIdentifier; int AcceptedAgreementFields = 0; }; @@ -55,10 +60,10 @@ namespace AppInstaller::Repository void SaveMetadata(const SourceDetailsInternal& details); // Checks the source agreements and returns if agreements are satisfied. - bool CheckSourceAgreements(const SourceDetails& details); + bool CheckSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields); // Save agreements information. - void SaveAcceptedSourceAgreements(const SourceDetails& details); + void SaveAcceptedSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields); // Removes all settings streams associated with the source list. // Implements `winget source reset --force`. diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -2,8 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include <mutex> -#include <AppInstallerRepositorySource.h> -#include <AppInstallerRepositorySearch.h> +#include <winget/RepositorySource.h> #include "CatalogPackage.h" #include "CatalogPackage.g.cpp" #include "PackageCatalog.h" @@ -14,7 +13,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation { void CatalogPackage::Initialize( - std::shared_ptr<const ::AppInstaller::Repository::ISource> source, + ::AppInstaller::Repository::Source source, std::shared_ptr<::AppInstaller::Repository::IPackage> package) { m_source = std::move(source); diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.h b/src/Microsoft.Management.Deployment/CatalogPackage.h @@ -11,7 +11,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Initialize( - std::shared_ptr<const ::AppInstaller::Repository::ISource> source, + ::AppInstaller::Repository::Source source, std::shared_ptr<::AppInstaller::Repository::IPackage> package); #endif @@ -25,7 +25,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: - std::shared_ptr<const ::AppInstaller::Repository::ISource> m_source; + ::AppInstaller::Repository::Source m_source; std::shared_ptr<::AppInstaller::Repository::IPackage> m_package; Windows::Foundation::Collections::IVector<winrt::Microsoft::Management::Deployment::PackageVersionId> m_availableVersions{ winrt::single_threaded_vector<winrt::Microsoft::Management::Deployment::PackageVersionId>() }; winrt::Microsoft::Management::Deployment::PackageVersionInfo m_installedVersion{ nullptr }; diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp @@ -2,8 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include <AppInstallerErrors.h> -#include <AppInstallerRepositorySearch.h> -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include "Microsoft/PredefinedInstalledSourceFactory.h" #include "Workflows/WorkflowBase.h" #include "Converters.h" diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h @@ -3,8 +3,7 @@ #pragma once #include "PackageMatchFilter.g.h" #include <AppInstallerArchitecture.h> -#include <AppInstallerRepositorySearch.h> -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include <Workflows/WorkflowBase.h> namespace winrt::Microsoft::Management::Deployment::implementation diff --git a/src/Microsoft.Management.Deployment/MatchResult.cpp b/src/Microsoft.Management.Deployment/MatchResult.cpp @@ -2,8 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include <mutex> -#include <AppInstallerRepositorySource.h> -#include <AppInstallerRepositorySearch.h> +#include <winget/RepositorySource.h> #include "MatchResult.h" #include "MatchResult.g.cpp" #include "CatalogPackage.h" diff --git a/src/Microsoft.Management.Deployment/PackageCatalog.cpp b/src/Microsoft.Management.Deployment/PackageCatalog.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include <mutex> -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include "Workflows/WorkflowBase.h" #include "Converters.h" #include "PackageCatalog.h" @@ -28,7 +28,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation { void PackageCatalog::Initialize( winrt::Microsoft::Management::Deployment::PackageCatalogInfo info, - std::shared_ptr<const ::AppInstaller::Repository::ISource> source, + ::AppInstaller::Repository::Source source, bool isComposite) { m_info = info; @@ -137,7 +137,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation } searchRequest.MaximumResults = options.ResultLimit(); - auto searchResult = m_source->Search(searchRequest); + auto searchResult = m_source.Search(searchRequest); // Handle failures by just rethrowing the first one for now. // TODO: Look into updating the COM interface to enable the single source diff --git a/src/Microsoft.Management.Deployment/PackageCatalog.h b/src/Microsoft.Management.Deployment/PackageCatalog.h @@ -12,7 +12,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Initialize( winrt::Microsoft::Management::Deployment::PackageCatalogInfo info, - std::shared_ptr<const ::AppInstaller::Repository::ISource> source, + ::AppInstaller::Repository::Source source, bool isComposite); #endif @@ -24,7 +24,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: winrt::Microsoft::Management::Deployment::PackageCatalogInfo m_info{ nullptr }; - std::shared_ptr<const ::AppInstaller::Repository::ISource> m_source; + ::AppInstaller::Repository::Source m_source; bool m_isComposite = false; #endif }; diff --git a/src/Microsoft.Management.Deployment/PackageCatalogInfo.cpp b/src/Microsoft.Management.Deployment/PackageCatalogInfo.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include "PackageCatalogInfo.h" #include "PackageCatalogInfo.g.cpp" #include <wil\cppwinrt_wrl.h> diff --git a/src/Microsoft.Management.Deployment/PackageCatalogInfo.h b/src/Microsoft.Management.Deployment/PackageCatalogInfo.h @@ -8,7 +8,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation struct PackageCatalogInfo : PackageCatalogInfoT<PackageCatalogInfo> { PackageCatalogInfo() = default; - + #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Initialize(const ::AppInstaller::Repository::SourceDetails& sourceDetails); ::AppInstaller::Repository::SourceDetails& GetSourceDetails(); @@ -24,7 +24,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: - ::AppInstaller::Repository::SourceDetails m_sourceDetails{}; + ::AppInstaller::Repository::SourceDetails m_sourceDetails; #endif }; } diff --git a/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp b/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include "PackageCatalogReference.h" #include "PackageCatalogReference.g.cpp" #include "PackageCatalogInfo.h" @@ -17,9 +17,10 @@ namespace winrt::Microsoft::Management::Deployment::implementation { - void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::PackageCatalogInfo packageCatalogInfo) + void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::PackageCatalogInfo packageCatalogInfo, ::AppInstaller::Repository::Source sourceReference) { m_info = packageCatalogInfo; + m_sourceReference = std::move(sourceReference); } void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions options) { @@ -54,40 +55,38 @@ namespace winrt::Microsoft::Management::Deployment::implementation } ::AppInstaller::ProgressCallback progress; - std::shared_ptr<::AppInstaller::Repository::ISource> source; + ::AppInstaller::Repository::Source source; if (m_compositePackageCatalogOptions) { - std::vector<std::shared_ptr<::AppInstaller::Repository::ISource>> remoteSources; + std::vector<::AppInstaller::Repository::Source> remoteSources; for (uint32_t i = 0; i < m_compositePackageCatalogOptions.Catalogs().Size(); ++i) { auto catalog = m_compositePackageCatalogOptions.Catalogs().GetAt(i); - winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo* catalogInfoImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>(catalog.Info()); - - std::shared_ptr<::AppInstaller::Repository::ISource> remoteSource = ::AppInstaller::Repository::OpenSourceFromDetails(catalogInfoImpl->GetSourceDetails(), progress).Source; - if (!remoteSource) - { - // If source is null, return the error. There's no way to get the hresult that caused the error right now. - return GetConnectCatalogErrorResult(); - } - remoteSources.emplace_back(std::move(remoteSource)); + winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference* catalogImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference>(catalog); + auto copy = catalogImpl->m_sourceReference; + copy.Open(progress); + remoteSources.emplace_back(std::move(copy)); } + + // Create the aggregated source. + source = ::AppInstaller::Repository::Source{ remoteSources }; + + // Create composite with installed source if needed. ::AppInstaller::Repository::CompositeSearchBehavior searchBehavior = GetRepositoryCompositeSearchBehavior(m_compositePackageCatalogOptions.CompositeSearchBehavior()); - std::shared_ptr<::AppInstaller::Repository::ISource> installedSource; // Check if search behavior indicates that the caller does not want to do local correlation. if (m_compositePackageCatalogOptions.CompositeSearchBehavior() != Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs) { - installedSource = ::AppInstaller::Repository::OpenPredefinedSource(::AppInstaller::Repository::PredefinedSource::Installed, progress); + ::AppInstaller::Repository::Source installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; + installedSource.Open(progress); + source = ::AppInstaller::Repository::Source{ installedSource, source, searchBehavior }; } - - // Create the composite source. - source = ::AppInstaller::Repository::CreateCompositeSource(installedSource, remoteSources, searchBehavior); } else { - winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo* catalogInfoImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>(m_info); - source = ::AppInstaller::Repository::OpenSourceFromDetails(catalogInfoImpl->GetSourceDetails(), progress).Source; + source = m_sourceReference; + source.Open(progress); } if (!source) @@ -99,7 +98,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation // Have to make another package catalog info because source->GetDetails has more fields than m_info does. // Specifically, Rest sources do not have the Ids filled in m_info since they only get the id from the rest server after being Opened. auto packageCatalogInfo = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>>(); - packageCatalogInfo->Initialize(source->GetDetails()); + packageCatalogInfo->Initialize(source.GetDetails()); auto connectResult = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::ConnectResult>>(); auto packageCatalog = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalog>>(); packageCatalog->Initialize(*packageCatalogInfo, source, (m_compositePackageCatalogOptions != nullptr)); @@ -109,21 +108,16 @@ namespace winrt::Microsoft::Management::Deployment::implementation catch (...) { } - auto connectResult = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::ConnectResult>>(); - connectResult->Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus::CatalogError, nullptr); - return *connectResult; + return GetConnectCatalogErrorResult(); } hstring PackageCatalogReference::AdditionalPackageCatalogArguments() { if (!IsComposite()) { - winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo* catalogInfoImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>(m_info); - ::AppInstaller::Repository::SourceDetails sourceDetails = catalogInfoImpl->GetSourceDetails(); - auto customHeader = catalogInfoImpl->GetSourceDetails().CustomHeader; - if (customHeader.has_value()) + if (m_additionalPackageCatalogArguments.has_value()) { - return winrt::to_hstring(customHeader.value()); + return winrt::to_hstring(m_additionalPackageCatalogArguments.value()); } } @@ -138,8 +132,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation } else { - winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo* catalogInfoImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>(m_info); - catalogInfoImpl->GetSourceDetails().CustomHeader = ::AppInstaller::Utility::ConvertToUTF8(value); + m_additionalPackageCatalogArguments = ::AppInstaller::Utility::ConvertToUTF8(value); + m_sourceReference.SetCustomHeader(m_additionalPackageCatalogArguments); } } } diff --git a/src/Microsoft.Management.Deployment/PackageCatalogReference.h b/src/Microsoft.Management.Deployment/PackageCatalogReference.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include "PackageCatalogReference.g.h" +#include <optional> namespace winrt::Microsoft::Management::Deployment::implementation { @@ -10,7 +11,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation PackageCatalogReference() = default; #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(winrt::Microsoft::Management::Deployment::PackageCatalogInfo packageCatalogInfo); + void Initialize(winrt::Microsoft::Management::Deployment::PackageCatalogInfo packageCatalogInfo, ::AppInstaller::Repository::Source sourceReference); void Initialize(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions options); #endif @@ -25,6 +26,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation private: winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions m_compositePackageCatalogOptions{ nullptr }; winrt::Microsoft::Management::Deployment::PackageCatalogInfo m_info{ nullptr }; + ::AppInstaller::Repository::Source m_sourceReference; + std::optional<std::string> m_additionalPackageCatalogArguments; #endif }; } diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -39,70 +39,77 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Management::Deployment::PackageCatalogReference> PackageManager::GetPackageCatalogs() { Windows::Foundation::Collections::IVector<Microsoft::Management::Deployment::PackageCatalogReference> catalogs{ winrt::single_threaded_vector<Microsoft::Management::Deployment::PackageCatalogReference>() }; - std::vector<::AppInstaller::Repository::SourceDetails> sources = ::AppInstaller::Repository::GetSources(); + std::vector<::AppInstaller::Repository::SourceDetails> sources = ::AppInstaller::Repository::Source::GetCurrentSources(); for (uint32_t i = 0; i < sources.size(); i++) { auto packageCatalogInfo = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>>(); - packageCatalogInfo->Initialize(sources.at(i)); + ::AppInstaller::Repository::Source sourceReference{ sources.at(i).Name }; + packageCatalogInfo->Initialize(sourceReference.GetDetails()); auto packageCatalogRef = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference>>(); - packageCatalogRef->Initialize(*packageCatalogInfo); + packageCatalogRef->Initialize(*packageCatalogInfo, sourceReference); catalogs.Append(*packageCatalogRef); } return catalogs.GetView(); } winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog) { - ::AppInstaller::Repository::SourceDetails sourceDetails; + ::AppInstaller::Repository::Source source; switch (predefinedPackageCatalog) { case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalog: - sourceDetails = GetWellKnownSourceDetails(::AppInstaller::Repository::WellKnownSource::WinGet); + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGet }; break; case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::MicrosoftStore: - sourceDetails = GetWellKnownSourceDetails(::AppInstaller::Repository::WellKnownSource::MicrosoftStore); + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::MicrosoftStore }; break; case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::DesktopFrameworks: - sourceDetails = GetWellKnownSourceDetails(::AppInstaller::Repository::WellKnownSource::DesktopFrameworks); + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::DesktopFrameworks }; break; default: throw hresult_invalid_argument(); } auto packageCatalogInfo = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>>(); - packageCatalogInfo->Initialize(sourceDetails); + packageCatalogInfo->Initialize(source.GetDetails()); auto packageCatalogRef = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference>>(); - packageCatalogRef->Initialize(*packageCatalogInfo); + packageCatalogRef->Initialize(*packageCatalogInfo, source); return *packageCatalogRef; } winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog) { - ::AppInstaller::Repository::SourceDetails sourceDetails; + ::AppInstaller::Repository::Source source; switch (localPackageCatalog) { case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstalledPackages: - sourceDetails = GetPredefinedSourceDetails(::AppInstaller::Repository::PredefinedSource::Installed); + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; break; case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstallingPackages: - sourceDetails = GetPredefinedSourceDetails(::AppInstaller::Repository::PredefinedSource::Installing); + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installing }; break; default: throw hresult_invalid_argument(); } auto packageCatalogInfo = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>>(); - packageCatalogInfo->Initialize(sourceDetails); + packageCatalogInfo->Initialize(source.GetDetails()); auto packageCatalogRef = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference>>(); - packageCatalogRef->Initialize(*packageCatalogInfo); + packageCatalogRef->Initialize(*packageCatalogInfo, source); return *packageCatalogRef; } winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPackageCatalogByName(hstring const& catalogName) { - std::optional<::AppInstaller::Repository::SourceDetails> source = ::AppInstaller::Repository::GetSource(winrt::to_string(catalogName)); + std::string name = winrt::to_string(catalogName); + if (name.empty()) + { + return nullptr; + } + + ::AppInstaller::Repository::Source source{ name }; // Create the catalog object if the source is found, otherwise return null. Don't throw. - if (source.has_value()) + if (source) { auto packageCatalogInfo = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>>(); - packageCatalogInfo->Initialize(source.value()); + packageCatalogInfo->Initialize(source.GetDetails()); auto packageCatalogRef = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference>>(); - packageCatalogRef->Initialize(*packageCatalogInfo); + packageCatalogRef->Initialize(*packageCatalogInfo, source); return *packageCatalogRef; } else diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -76,7 +76,7 @@ namespace Microsoft.Management.Deployment HRESULT ExtendedErrorCode{ get; }; } - /// IMPLEMENTATION NOTE: SourceOrigin from AppInstallerRepositorySource.h + /// IMPLEMENTATION NOTE: SourceOrigin from winget/RepositorySource.h /// Defines the origin of the package catalog details. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] enum PackageCatalogOrigin @@ -87,7 +87,7 @@ namespace Microsoft.Management.Deployment User, }; - /// IMPLEMENTATION NOTE: SourceTrustLevel from AppInstallerRepositorySource.h + /// IMPLEMENTATION NOTE: SourceTrustLevel from winget/RepositorySource.h /// Defines the trust level of the package catalog. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] enum PackageCatalogTrustLevel @@ -96,7 +96,7 @@ namespace Microsoft.Management.Deployment Trusted, }; - /// IMPLEMENTATION NOTE: SourceDetails from AppInstallerRepositorySource.h + /// IMPLEMENTATION NOTE: SourceDetails from winget/RepositorySource.h /// Interface for retrieving information about an package catalog without acting on it. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass PackageCatalogInfo @@ -158,25 +158,25 @@ namespace Microsoft.Management.Deployment Greater, }; - /// IMPLEMENTATION NOTE: IPackageVersion from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: IPackageVersion from winget/RepositorySearch.h /// A single package version. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass PackageVersionInfo { - /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from winget/RepositorySearch.h /// Gets any metadata associated with this package version. /// Primarily stores data on installed packages. /// Metadata fields may have no value (e.g. packages that aren't installed will not have an InstalledLocation). String GetMetadata(PackageVersionMetadataField metadataField); - /// IMPLEMENTATION NOTE: PackageVersionProperty fields from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageVersionProperty fields from winget/RepositorySearch.h String Id { get; }; String DisplayName { get; }; String Version { get; }; String Channel { get; }; - /// DESIGN NOTE: RelativePath from AppInstallerRepositorySearch.h is excluded as not needed. + /// DESIGN NOTE: RelativePath from winget/RepositorySearch.h is excluded as not needed. /// String RelativePath; - /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from winget/RepositorySearch.h /// PackageFamilyName and ProductCode can have multiple values. Windows.Foundation.Collections.IVectorView<String> PackageFamilyNames { get; }; Windows.Foundation.Collections.IVectorView<String> ProductCodes { get; }; @@ -196,13 +196,13 @@ namespace Microsoft.Management.Deployment } /// DESIGN NOTE: - /// GetManifest from IPackageVersion in AppInstallerRepositorySearch is not implemented in V1. That class has + /// GetManifest from IPackageVersion in winget/RepositorySearch is not implemented in V1. That class has /// a lot of fields and no one requesting it. /// Gets the manifest of this package version. /// virtual Manifest::Manifest GetManifest() = 0; } - /// IMPLEMENTATION NOTE: PackageVersionKey from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageVersionKey from winget/RepositorySearch.h /// A key to identify a package version within a package. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass PackageVersionId @@ -215,12 +215,12 @@ namespace Microsoft.Management.Deployment String Channel { get; }; }; - /// IMPLEMENTATION NOTE: IPackage from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: IPackage from winget/RepositorySearch.h /// A package, potentially containing information about it's local state and the available versions. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass CatalogPackage { - /// IMPLEMENTATION NOTE: PackageProperty fields from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageProperty fields from winget/RepositorySearch.h /// Gets a property of this package. String Id { get; }; String Name { get; }; @@ -241,12 +241,12 @@ namespace Microsoft.Management.Deployment Boolean IsUpdateAvailable { get; }; /// DESIGN NOTE: - /// IsSame from IPackage in AppInstallerRepositorySearch is not implemented in V1. + /// IsSame from IPackage in winget/RepositorySearch is not implemented in V1. /// Determines if the given IPackage refers to the same package as this one. /// virtual bool IsSame(const IPackage*) const = 0; } - /// IMPLEMENTATION NOTE: CompositeSearchBehavior from AppInstallerRepositorySource.h + /// IMPLEMENTATION NOTE: CompositeSearchBehavior from winget/RepositorySource.h /// Search behavior for composite catalogs. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] enum CompositeSearchBehavior @@ -261,7 +261,7 @@ namespace Microsoft.Management.Deployment AllCatalogs, }; - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] enum PackageFieldMatchOption { @@ -271,7 +271,7 @@ namespace Microsoft.Management.Deployment ContainsCaseInsensitive, }; - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h /// The field to match on. /// The values must be declared in order of preference in search results. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] @@ -283,13 +283,13 @@ namespace Microsoft.Management.Deployment Moniker, Command, Tag, - /// DESIGN NOTE: The following PackageFieldMatchOption from AppInstallerRepositorySearch.h are not implemented in V1. + /// DESIGN NOTE: The following PackageFieldMatchOption from winget/RepositorySearch.h are not implemented in V1. /// PackageFamilyName, /// ProductCode, /// NormalizedNameAndPublisher, }; - /// IMPLEMENTATION NOTE: PackageMatchFilter from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: PackageMatchFilter from winget/RepositorySearch.h [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass PackageMatchFilter { @@ -300,10 +300,10 @@ namespace Microsoft.Management.Deployment PackageMatchField Field; /// The value to match String Value; - /// DESIGN NOTE: "Additional" from RequestMatch AppInstallerRepositorySearch.h is not implemented here. + /// DESIGN NOTE: "Additional" from RequestMatch winget/RepositorySearch.h is not implemented here. } - /// IMPLEMENTATION NOTE: MatchResult from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: MatchResult from winget/RepositorySearch.h /// A single result from the search. [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass MatchResult @@ -326,7 +326,7 @@ namespace Microsoft.Management.Deployment InvalidOptions }; - /// IMPLEMENTATION NOTE: SearchResult from AppInstallerRepositorySearch.h + /// IMPLEMENTATION NOTE: SearchResult from winget/RepositorySearch.h /// Search result data returned from FindPackages [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass FindPackagesResult @@ -350,7 +350,7 @@ namespace Microsoft.Management.Deployment FindPackagesOptions(); /// DESIGN NOTE: - /// This class maps to SearchRequest from AppInstallerRepositorySearch.h + /// This class maps to SearchRequest from winget/RepositorySearch.h /// That class is a container for data used to filter the available manifests in an package catalog. /// Its properties can be thought of as: /// (Query || Inclusions...) && Filters... @@ -371,7 +371,7 @@ namespace Microsoft.Management.Deployment UInt32 ResultLimit; } - /// IMPLEMENTATION NOTE: ISource from AppInstallerRepositorySource.h + /// IMPLEMENTATION NOTE: Source from winget/RepositorySource.h /// A catalog for searching for packages [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] runtimeclass PackageCatalog diff --git a/src/Microsoft.Management.Deployment/PackageMatchFilter.cpp b/src/Microsoft.Management.Deployment/PackageMatchFilter.cpp @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include <AppInstallerRepositorySource.h> -#include <AppInstallerRepositorySearch.h> +#include <winget/RepositorySource.h> #include "Workflows/WorkflowBase.h" #include "Converters.h" #pragma warning( push ) diff --git a/src/Microsoft.Management.Deployment/PackageVersionId.cpp b/src/Microsoft.Management.Deployment/PackageVersionId.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include "PackageVersionId.h" #include "PackageVersionId.g.cpp" #include <wil\cppwinrt_wrl.h> diff --git a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include <mutex> -#include <AppInstallerRepositorySource.h> +#include <winget/RepositorySource.h> #include "PackageVersionInfo.h" #include "PackageVersionInfo.g.cpp" #include "PackageCatalogInfo.h" @@ -81,7 +81,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation if (!m_packageCatalog) { auto packageCatalogInfo = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalogInfo>>(); - packageCatalogInfo->Initialize(m_packageVersion->GetSource()->GetDetails()); + packageCatalogInfo->Initialize(m_packageVersion->GetSource().GetDetails()); auto packageCatalog = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::PackageCatalog>>(); packageCatalog->Initialize(*packageCatalogInfo, m_packageVersion->GetSource(), false); m_packageCatalog = *packageCatalog;