winget-cli

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

commit c5fbdd9fb9d53336bad182b82f255b2e3f691b10
parent a3c686b52e2a8c779f84c66a430272a49dfe51f3
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Tue, 27 Feb 2024 09:47:17 -0800

Refactor `IPackage` (#4174)

## Change
To better reflect the underlying code and continue to move business
logic out of the `CompositeSource`, this change refactors the `IPackage`
interface into 3:

```C++
    // Contains a collection of package versions.
    struct IPackageVersionCollection
    {
        // Gets all versions of this package.
        // The versions will be returned in sorted, descending order.
        //  Ex. { 4, 3, 2, 1 }
        virtual std::vector<PackageVersionKey> GetVersionKeys() const = 0;

        // Gets a specific version of this package.
        virtual std::shared_ptr<IPackageVersion> GetVersion(const PackageVersionKey& versionKey) const = 0;

        // A convenience method to effectively call `GetVersion(GetVersionKeys[0])`.
        virtual std::shared_ptr<IPackageVersion> GetLatestVersion() const = 0;
    };

    // Contains information about a package and its versions from a single source.
    struct IPackage : public IPackageVersionCollection
    {
        // Gets a property of this package.
        virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0;

        // Gets the source that this package is from.
        virtual Source GetSource() const = 0;

        // Determines if the given IPackage refers to the same package as this one.
        virtual bool IsSame(const IPackage*) const = 0;

        // Gets this object as the requested type, or null if it is not the requested type.
        virtual const void* CastTo(IPackageType type) const = 0;
    };

    // Contains information about the graph of packages related to a search.
    struct ICompositePackage
    {
        // Gets a property of this package result.
        virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0;

        // Gets the installed package information.
        virtual std::shared_ptr<IPackage> GetInstalled() = 0;

        // Gets all of the available packages for this result.
        // There will be at most one package per source in this list.
        virtual std::vector<std::shared_ptr<IPackage>> GetAvailable() = 0;
    };
```

`IPackageVersionCollection` is simply a set of `IPackageVersion` objects
with only the previous `IPackage` functions to enumerate them. This
enables some code that only wants to inspect versions to use a tighter
set of APIs and thus some implementations need not worry about things
like the appropriate property values to return.

The refactored `IPackage` inherits from `IPackageVersionCollection` and
is itself a set of package versions that all come from a single source
(as in `Source`). It adds the properties and the `Source` that the
versions are all from.

`ICompositePackage` is the new search result, resembling the previous
`IPackage` in what it contains but exposing it more directly. The
installed `IPackage` object will eventually be able to contain more than
one installed version in a future change. The available packages are
exposed directly as `IPackage` objects, rather than mixing the versions
together in the `CompositeSource`.

The code in `PackageVersionSelection.cpp` extracts the business logic
that was inside `CompositeSource` previously, as well as providing a few
helpers to make the existing code work more succinctly. The refactoring
is not attempting to change behavior, but simply make it possible to
change behavior more easily and precisely in the future.

I would have preferred to go further by splitting `CompositeSource` out
into a public type, allowing the other sources to have a simpler model
of only ever returning `IPackage` results from search. This change would
have created an even larger churn though and did not directly contribute
towards the goal of supporting future side-by-side work.
  
Also changes the YAML object model to use a non-exceptional function to
attempt to convert to an integer.
Diffstat:
Msrc/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.cpp | 3++-
Msrc/AppInstallerCLICore/ExecutionContextData.h | 2+-
Msrc/AppInstallerCLICore/Workflows/CompletionFlow.cpp | 20+++++++++++++-------
Msrc/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp | 10++++++----
Msrc/AppInstallerCLICore/Workflows/ImportExportFlow.cpp | 13++++++++-----
Msrc/AppInstallerCLICore/Workflows/PinFlow.cpp | 30++++++++++++++++--------------
Msrc/AppInstallerCLICore/Workflows/RepairFlow.cpp | 6++++--
Msrc/AppInstallerCLICore/Workflows/UninstallFlow.cpp | 22++++++++++++++++------
Msrc/AppInstallerCLICore/Workflows/UpdateFlow.cpp | 11++++++-----
Msrc/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 35+++++++++++++++++++----------------
Msrc/AppInstallerCLITests/ARPChanges.cpp | 23++++++++++++-----------
Msrc/AppInstallerCLITests/CompositeSource.cpp | 190++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/AppInstallerCLITests/DependenciesTestSource.h | 6+++---
Msrc/AppInstallerCLITests/PackageTrackingCatalog.cpp | 17+++++++++++------
Msrc/AppInstallerCLITests/SQLiteIndexSource.cpp | 20++++++++++++--------
Msrc/AppInstallerCLITests/Sources.cpp | 6+++---
Msrc/AppInstallerCLITests/TestSource.cpp | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Msrc/AppInstallerCLITests/TestSource.h | 42+++++++++++++++++++++++++++++++++++-------
Msrc/AppInstallerCLITests/WorkflowCommon.cpp | 92++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/AppInstallerRepositoryCore/ARPCorrelation.cpp | 20++++++++++----------
Msrc/AppInstallerRepositoryCore/ARPCorrelationAlgorithms.cpp | 4++--
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj | 3+++
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters | 27+++++++++++++++++++++------
Msrc/AppInstallerRepositoryCore/CompositeSource.cpp | 299+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp | 197+++++++++++++++++++++++++------------------------------------------------------
Msrc/AppInstallerRepositoryCore/PackageInstalledStatus.cpp | 17++++++++++-------
Asrc/AppInstallerRepositoryCore/PackageVersionSelection.cpp | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/PinningData.cpp | 8++++----
Asrc/AppInstallerRepositoryCore/Public/winget/InstalledStatus.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h | 28++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/Public/winget/PinningData.h | 2+-
Msrc/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h | 118++++++++++++++++++++++++++++++-------------------------------------------------
Msrc/AppInstallerRepositoryCore/Public/winget/RepositorySource.h | 5+++++
Msrc/AppInstallerRepositoryCore/RepositorySearch.cpp | 8++++++++
Msrc/AppInstallerRepositoryCore/RepositorySource.cpp | 13+++++++++++++
Msrc/AppInstallerRepositoryCore/Rest/RestSource.cpp | 55+++++++++++++++++++++++++++++++++----------------------
Msrc/AppInstallerSharedLib/Yaml.cpp | 13++++++++-----
Msrc/Microsoft.Management.Deployment/CatalogPackage.cpp | 259++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/Microsoft.Management.Deployment/CatalogPackage.h | 6+++---
Msrc/Microsoft.Management.Deployment/InstalledStatus.h | 1+
Msrc/Microsoft.Management.Deployment/PackageInstallerInstalledStatus.h | 1+
Msrc/Microsoft.Management.Deployment/PackageManager.cpp | 2+-
42 files changed, 1140 insertions(+), 801 deletions(-)

diff --git a/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.cpp b/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.cpp @@ -358,7 +358,8 @@ namespace AppInstaller::CLI::Configuration { if (!package.Version.empty()) { - auto versionKeys = searchResult.Matches.at(0).Package->GetAvailableVersionKeys(); + std::shared_ptr<Repository::IPackage> availablePackage = searchResult.Matches.at(0).Package->GetAvailable().at(0); + auto versionKeys = availablePackage->GetVersionKeys(); bool foundVersion = false; for (auto const& versionKey : versionKeys) { diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h @@ -105,7 +105,7 @@ namespace AppInstaller::CLI::Execution template <> struct DataMapping<Data::Package> { - using value_t = std::shared_ptr<Repository::IPackage>; + using value_t = std::shared_ptr<Repository::ICompositePackage>; }; template <> diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -70,11 +70,14 @@ namespace AppInstaller::CLI::Workflow const std::string& word = context.Get<Data::CompletionData>().Word(); auto stream = context.Reporter.Completion(); - for (const auto& vc : context.Get<Execution::Data::Package>()->GetAvailableVersionKeys()) + for (const auto& ap : context.Get<Execution::Data::Package>()->GetAvailable()) { - if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) + for (const auto& vc : ap->GetVersionKeys()) { - OutputCompletionString(stream, vc.Version); + if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) + { + OutputCompletionString(stream, vc.Version); + } } } } @@ -86,12 +89,15 @@ namespace AppInstaller::CLI::Workflow std::vector<std::string> channels; - for (const auto& vc : context.Get<Execution::Data::Package>()->GetAvailableVersionKeys()) + for (const auto& ap : context.Get<Execution::Data::Package>()->GetAvailable()) { - if ((word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Channel, word)) && - std::find(channels.begin(), channels.end(), vc.Channel) == channels.end()) + for (const auto& vc : ap->GetVersionKeys()) { - channels.emplace_back(vc.Channel); + if ((word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Channel, word)) && + std::find(channels.begin(), channels.end(), vc.Channel) == channels.end()) + { + channels.emplace_back(vc.Channel); + } } } diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -4,6 +4,7 @@ #include "DependencyNodeProcessor.h" #include "ManifestComparator.h" #include <winget/PinningData.h> +#include <winget/PackageVersionSelection.h> using namespace AppInstaller::Manifest; using namespace AppInstaller::Repository; @@ -41,20 +42,21 @@ namespace AppInstaller::CLI::Workflow const auto& match = matches.at(0); const auto& package = match.Package; auto packageId = package->GetProperty(PackageProperty::Id); - m_nodePackageInstalledVersion = package->GetInstalledVersion(); + m_nodePackageInstalledVersion = GetInstalledVersion(package); + std::shared_ptr<IPackageVersionCollection> availableVersions = GetAvailableVersionsForInstalledVersion(package); if (m_context.Args.Contains(Execution::Args::Type::Force)) { - m_nodePackageLatestVersion = package->GetLatestAvailableVersion(); + m_nodePackageLatestVersion = availableVersions->GetLatestVersion(); } else { Pinning::PinBehavior pinBehavior = m_context.Args.Contains(Execution::Args::Type::IncludePinned) ? Pinning::PinBehavior::IncludePinned : Pinning::PinBehavior::ConsiderPins; Pinning::PinningData pinningData{ Pinning::PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, package->GetInstalledVersion()); + auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, m_nodePackageInstalledVersion); - m_nodePackageLatestVersion = evaluator.GetLatestAvailableVersionForPins(package); + m_nodePackageLatestVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); } if (m_nodePackageInstalledVersion && dependencyNode.IsVersionOk(Utility::Version(m_nodePackageInstalledVersion->GetProperty(PackageVersionProperty::Version)))) diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -9,6 +9,7 @@ #include "WorkflowBase.h" #include <winget/RepositorySearch.h> #include <winget/Runtime.h> +#include <winget/PackageVersionSelection.h> namespace AppInstaller::CLI::Workflow { @@ -60,20 +61,22 @@ namespace AppInstaller::CLI::Workflow // If requested, checks that the installed version is available and reports a warning if it is not. std::shared_ptr<IPackageVersion> GetAvailableVersionForInstalledPackage( Execution::Context& context, - std::shared_ptr<IPackage> package, + std::shared_ptr<ICompositePackage> package, Utility::LocIndView version, Utility::LocIndView channel, bool checkVersion) { + std::shared_ptr<IPackageVersionCollection> availableVersions = GetAvailableVersionsForInstalledVersion(package); + if (!checkVersion) { - return package->GetLatestAvailableVersion(); + return availableVersions->GetLatestVersion(); } - auto availablePackageVersion = package->GetAvailableVersion({ "", version, channel }); + auto availablePackageVersion = availableVersions->GetVersion({ "", version, channel }); if (!availablePackageVersion) { - availablePackageVersion = package->GetLatestAvailableVersion(); + availablePackageVersion = availableVersions->GetLatestVersion(); if (availablePackageVersion) { // Warn installed version is not available. @@ -100,7 +103,7 @@ namespace AppInstaller::CLI::Workflow auto& exportedSources = exportedPackages.Sources; for (const auto& packageMatch : searchResult.Matches) { - auto installedPackageVersion = packageMatch.Package->GetInstalledVersion(); + auto installedPackageVersion = GetInstalledVersion(packageMatch.Package); auto version = installedPackageVersion->GetProperty(PackageVersionProperty::Version); auto channel = installedPackageVersion->GetProperty(PackageVersionProperty::Channel); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -6,6 +6,7 @@ #include "TableOutput.h" #include <winget/PinningData.h> #include <winget/RepositorySearch.h> +#include <winget/PackageVersionSelection.h> using namespace AppInstaller::Repository; @@ -38,7 +39,7 @@ namespace AppInstaller::CLI::Workflow if (context.Args.Contains(Execution::Args::Type::PinInstalled)) { - auto installedVersion = package->GetInstalledVersion(); + auto installedVersion = GetInstalledVersion(package); if (installedVersion) { pinKeys.emplace(Pinning::PinKey::GetPinKeyForInstalled(installedVersion->GetProperty(PackageVersionProperty::Id))); @@ -46,13 +47,12 @@ namespace AppInstaller::CLI::Workflow } else { - auto packageVersionKeys = package->GetAvailableVersionKeys(); - for (const auto& versionKey : packageVersionKeys) + auto availablePackages = package->GetAvailable(); + for (const auto& availablePackage : availablePackages) { - auto availableVersion = package->GetAvailableVersion(versionKey); pinKeys.emplace( - availableVersion->GetProperty(PackageVersionProperty::Id).get(), - availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get()); + availablePackage->GetProperty(PackageProperty::Id).get(), + availablePackage->GetSource().GetIdentifier()); } } @@ -140,7 +140,7 @@ namespace AppInstaller::CLI::Workflow } else { - auto availableVersion = package->GetAvailableVersion({ pinKey.SourceId, "", "" }); + auto availableVersion = GetAvailablePackageFromSource(package, pinKey.SourceId)->GetLatestVersion(); if (availableVersion) { packageNameToReport = availableVersion->GetProperty(PackageVersionProperty::Name); @@ -198,8 +198,6 @@ namespace AppInstaller::CLI::Workflow // Note that if a source was specified in the command line, // that will be the only one we get version keys from. // So, we remove pins from all sources unless one was provided. - auto packageVersionKeys = package->GetAvailableVersionKeys(); - for (const auto& pin : pins) { AICLI_LOG(CLI, Info, << "Removing Pin " << pin.GetKey().ToString()); @@ -255,15 +253,19 @@ namespace AppInstaller::CLI::Workflow else { // This ensures we get the info from the right source if it exists on multiple - auto availableVersion = match.Package->GetAvailableVersion({ pinKey.SourceId, "", "" }); - if (availableVersion) + auto availablePackage = GetAvailablePackageFromSource(match.Package, pinKey.SourceId); + if (availablePackage) { - packageName = availableVersion->GetProperty(PackageVersionProperty::Name); - sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName); + auto availableVersion = availablePackage->GetLatestVersion(); + if (availableVersion) + { + packageName = availableVersion->GetProperty(PackageVersionProperty::Name); + sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName); + } } } - auto installedVersion = match.Package->GetInstalledVersion(); + auto installedVersion = GetInstalledVersion(match.Package); if (installedVersion) { packageName = installedVersion->GetProperty(PackageVersionProperty::Name); diff --git a/src/AppInstallerCLICore/Workflows/RepairFlow.cpp b/src/AppInstallerCLICore/Workflows/RepairFlow.cpp @@ -14,6 +14,7 @@ #include "AppInstallerSynchronization.h" #include "MSStoreInstallerHandler.h" #include "ManifestComparator.h" +#include <winget/PackageVersionSelection.h> using namespace AppInstaller::Manifest; using namespace AppInstaller::Msix; @@ -443,10 +444,11 @@ namespace AppInstaller::CLI::Workflow std::string_view requestedVersion = context.Args.Contains(Execution::Args::Type::Version) ? context.Args.GetArg(Execution::Args::Type::Version) : installedVersion.ToString(); // If it's Store source with only one version unknown, use the unknown version for available version mapping. const auto& package = context.Get<Execution::Data::Package>(); - auto versionKeys = package->GetAvailableVersionKeys(); + auto packageVersions = GetAvailableVersionsForInstalledVersion(package, installedPackage); + auto versionKeys = packageVersions->GetVersionKeys(); if (versionKeys.size() == 1) { - auto packageVersion = package->GetAvailableVersion(versionKeys.at(0)); + auto packageVersion = packageVersions->GetVersion(versionKeys.at(0)); if (packageVersion->GetSource().IsWellKnownSource(WellKnownSource::MicrosoftStore) && Utility::Version{ packageVersion->GetProperty(PackageVersionProperty::Version) }.IsUnknown()) { diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -11,6 +11,7 @@ #include <AppInstallerDeployment.h> #include <AppInstallerSynchronization.h> #include <winget/Runtime.h> +#include <winget/PackageVersionSelection.h> using namespace AppInstaller::CLI::Execution; using namespace AppInstaller::Manifest; @@ -34,9 +35,8 @@ namespace AppInstaller::CLI::Workflow std::string SourceIdentifier; }; - void AddIfRemoteAndNotPresent(const std::shared_ptr<IPackageVersion>& packageVersion) + void AddIfRemoteAndNotPresent(Source&& source, const Utility::LocIndString& identifier) { - auto source = packageVersion->GetSource(); const auto details = source.GetDetails(); if (!source.ContainsAvailablePackages()) { @@ -51,7 +51,17 @@ namespace AppInstaller::CLI::Workflow } } - Items.emplace_back(Item{ packageVersion->GetProperty(PackageVersionProperty::Id), std::move(source), details.Identifier }); + Items.emplace_back(Item{ identifier, std::move(source), details.Identifier }); + } + + void AddIfRemoteAndNotPresent(const std::shared_ptr<IPackageVersion>& packageVersion) + { + AddIfRemoteAndNotPresent(packageVersion->GetSource(), packageVersion->GetProperty(PackageVersionProperty::Id)); + } + + void AddIfRemoteAndNotPresent(const std::shared_ptr<IPackage>& package) + { + AddIfRemoteAndNotPresent(package->GetSource(), package->GetProperty(PackageProperty::Id)); } std::vector<Item> Items; @@ -311,12 +321,12 @@ namespace AppInstaller::CLI::Workflow UninstallCorrelatedSources correlatedSources; // Start with the installed version - correlatedSources.AddIfRemoteAndNotPresent(package->GetInstalledVersion()); + correlatedSources.AddIfRemoteAndNotPresent(GetInstalledVersion(package)); // Then look through all available versions - for (const auto& versionKey : package->GetAvailableVersionKeys()) + for (const auto& availablePackage : package->GetAvailable()) { - correlatedSources.AddIfRemoteAndNotPresent(package->GetAvailableVersion(versionKey)); + correlatedSources.AddIfRemoteAndNotPresent(availablePackage); } // Finally record the uninstall for each found value diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. - #include "pch.h" #include "WorkflowBase.h" #include "DependenciesFlow.h" @@ -8,6 +7,7 @@ #include "UpdateFlow.h" #include "ManifestComparator.h" #include <winget/PinningData.h> +#include <winget/PackageVersionSelection.h> using namespace AppInstaller::Repository; using namespace AppInstaller::Repository::Microsoft; @@ -72,10 +72,11 @@ namespace AppInstaller::CLI::Workflow const bool includePinned = m_isSinglePackage || context.Args.Contains(Execution::Args::Type::IncludePinned); PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(includePinned ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins, package->GetInstalledVersion()); + auto evaluator = pinningData.CreatePinStateEvaluator(includePinned ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins, GetInstalledVersion(package)); // The version keys should have already been sorted by version - const auto& versionKeys = package->GetAvailableVersionKeys(); + auto availableVersions = GetAvailableVersionsForInstalledVersion(package); + const auto& versionKeys = availableVersions->GetVersionKeys(); // Assume that no update versions are applicable bool upgradeVersionAvailable = false; for (const auto& key : versionKeys) @@ -89,7 +90,7 @@ namespace AppInstaller::CLI::Workflow upgradeVersionAvailable = true; } - auto packageVersion = package->GetAvailableVersion(key); + auto packageVersion = availableVersions->GetVersion(key); // Check if the package is pinned PinType pinType = evaluator.EvaluatePinType(packageVersion); @@ -217,7 +218,7 @@ namespace AppInstaller::CLI::Workflow auto updateContextPtr = context.CreateSubContext(); Execution::Context& updateContext = *updateContextPtr; auto previousThreadGlobals = updateContext.SetForCurrentThread(); - auto installedVersion = match.Package->GetInstalledVersion(); + auto installedVersion = GetInstalledVersion(match.Package); updateContext.Add<Execution::Data::Package>(match.Package); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -11,6 +11,7 @@ #include <winget/Pin.h> #include <winget/PinningData.h> #include <winget/Runtime.h> +#include <winget/PackageVersionSelection.h> using namespace std::string_literals; using namespace AppInstaller::Utility::literals; @@ -628,7 +629,7 @@ namespace AppInstaller::CLI::Workflow for (size_t i = 0; i < searchResult.Matches.size(); ++i) { - auto latestVersion = searchResult.Matches[i].Package->GetLatestAvailableVersion(); + auto latestVersion = GetAllAvailableVersions(searchResult.Matches[i].Package)->GetLatestVersion(); table.OutputLine({ latestVersion->GetProperty(PackageVersionProperty::Name), @@ -739,10 +740,10 @@ namespace AppInstaller::CLI::Workflow auto package = searchResult.Matches[i].Package; std::string sourceName; - auto latest = package->GetLatestAvailableVersion(); - if (latest) + auto available = package->GetAvailable(); + if (!available.empty()) { - auto source = latest->GetSource(); + auto source = available[0]->GetSource(); if (source) { sourceName = source.GetDetails().Name; @@ -799,13 +800,14 @@ namespace AppInstaller::CLI::Workflow for (const auto& match : searchResult.Matches) { - auto installedVersion = match.Package->GetInstalledVersion(); + auto installedVersion = GetInstalledVersion(match.Package); if (installedVersion) { auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, installedVersion); + auto availableVersions = GetAvailableVersionsForInstalledVersion(match.Package, installedVersion); - auto latestVersion = evaluator.GetLatestAvailableVersionForPins(match.Package); + auto latestVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); bool updateAvailable = evaluator.IsUpdate(latestVersion); bool updateIsPinned = false; @@ -819,7 +821,7 @@ namespace AppInstaller::CLI::Workflow if (m_onlyShowUpgrades && !updateAvailable) { // Reuse the evaluator to check if there is an update outside of the pinning - auto unpinnedLatestVersion = match.Package->GetLatestAvailableVersion(); + auto unpinnedLatestVersion = availableVersions->GetLatestVersion(); bool updateAvailableWithoutPins = evaluator.IsUpdate(unpinnedLatestVersion); if (updateAvailableWithoutPins) @@ -996,7 +998,7 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND); } - std::shared_ptr<IPackage> package = searchResult.Matches.at(0).Package; + std::shared_ptr<ICompositePackage> package = searchResult.Matches.at(0).Package; Logging::Telemetry().LogAppFound(package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); context.Add<Execution::Data::Package>(std::move(package)); @@ -1007,8 +1009,9 @@ namespace AppInstaller::CLI::Workflow { PackageVersionKey key("", m_version, m_channel); - std::shared_ptr<IPackage> package = context.Get<Execution::Data::Package>(); + std::shared_ptr<ICompositePackage> package = context.Get<Execution::Data::Package>(); std::shared_ptr<IPackageVersion> requestedVersion; + auto availableVersions = GetAvailableVersionsForInstalledVersion(package); if (m_considerPins) { @@ -1026,17 +1029,17 @@ namespace AppInstaller::CLI::Workflow } PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, package->GetInstalledVersion()); + auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, GetInstalledVersion(package)); // TODO: The logic here will probably have to get more difficult once we support channels if (Utility::IsEmptyOrWhitespace(m_version) && Utility::IsEmptyOrWhitespace(m_channel)) { - requestedVersion = evaluator.GetLatestAvailableVersionForPins(package); + requestedVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); if (!requestedVersion) { // Check whether we didn't find the latest version because it was pinned or because there wasn't one - auto latestVersion = package->GetLatestAvailableVersion(); + auto latestVersion = availableVersions->GetLatestVersion(); if (latestVersion) { isPinned = true; @@ -1045,7 +1048,7 @@ namespace AppInstaller::CLI::Workflow } else { - requestedVersion = package->GetAvailableVersion(key); + requestedVersion = availableVersions->GetVersion(key); isPinned = evaluator.EvaluatePinType(requestedVersion) != PinType::Unknown; } @@ -1066,7 +1069,7 @@ namespace AppInstaller::CLI::Workflow else { // The simple case: Just look up the requested version - requestedVersion = package->GetAvailableVersion(key); + requestedVersion = availableVersions->GetVersion(key); } std::optional<Manifest::Manifest> manifest; @@ -1346,7 +1349,7 @@ namespace AppInstaller::CLI::Workflow void GetInstalledPackageVersion(Execution::Context& context) { - context.Add<Execution::Data::InstalledPackageVersion>(context.Get<Execution::Data::Package>()->GetInstalledVersion()); + context.Add<Execution::Data::InstalledPackageVersion>(GetInstalledVersion(context.Get<Execution::Data::Package>())); } void ReportExecutionStage::operator()(Execution::Context& context) const @@ -1356,7 +1359,7 @@ namespace AppInstaller::CLI::Workflow void ShowAppVersions(Execution::Context& context) { - auto versions = context.Get<Execution::Data::Package>()->GetAvailableVersionKeys(); + auto versions = GetAllAvailableVersions(context.Get<Execution::Data::Package>())->GetVersionKeys(); Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); for (const auto& version : versions) diff --git a/src/AppInstallerCLITests/ARPChanges.cpp b/src/AppInstallerCLITests/ARPChanges.cpp @@ -9,6 +9,7 @@ #include <winget/Manifest.h> #include <winget/ARPCorrelationAlgorithms.h> #include <Microsoft/PredefinedInstalledSourceFactory.h> +#include <winget/PackageVersionSelection.h> using namespace TestCommon; using namespace AppInstaller; @@ -133,7 +134,7 @@ struct ARPTestContext : public Context AddResult(MatchResult, id, name, publisher, version); } - void ExpectEvent(size_t arpChanges, size_t matches, size_t overlap, IPackage* arpEntry = nullptr) + void ExpectEvent(size_t arpChanges, size_t matches, size_t overlap, const std::shared_ptr<ICompositePackage>& arpEntry = nullptr) { REQUIRE(Logger->WasLogSuccessfulInstallARPChangeCalled); @@ -149,7 +150,7 @@ struct ARPTestContext : public Context if (arpEntry) { - auto version = arpEntry->GetInstalledVersion(); + auto version = GetInstalledVersion(arpEntry); REQUIRE(version->GetProperty(PackageVersionProperty::Name) == ARPName); REQUIRE(version->GetProperty(PackageVersionProperty::Version) == ARPVersion); @@ -202,7 +203,7 @@ struct ARPTestContext : public Context TestPackage::MetadataMap metadata; metadata[PackageVersionMetadata::Publisher] = publisher; - result.Matches.emplace_back(TestPackage::Make(manifest, std::move(metadata), std::vector<Manifest::Manifest>{}, Source), defaultFilter); + result.Matches.emplace_back(TestCompositePackage::Make(manifest, std::move(metadata), std::vector<Manifest::Manifest>{}, Source), defaultFilter); } }; @@ -259,8 +260,8 @@ TEST_CASE("ARPChanges_CheckSnapshot", "[ARPChanges][workflow]") { if (match.Package->GetProperty(PackageProperty::Id) == std::get<0>(*itr)) { - REQUIRE(match.Package->GetInstalledVersion()->GetProperty(PackageVersionProperty::Version) == std::get<1>(*itr)); - REQUIRE(match.Package->GetInstalledVersion()->GetProperty(PackageVersionProperty::Channel) == std::get<2>(*itr)); + REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Version) == std::get<1>(*itr)); + REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Channel) == std::get<2>(*itr)); snapshot.erase(itr); found = true; @@ -296,7 +297,7 @@ TEST_CASE("ARPChanges_NoChange_SingleMatch", "[ARPChanges][workflow]") context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); context << ReportARPChanges; - context.ExpectEvent(0, 1, 0, context.MatchResult.Matches[0].Package.get()); + context.ExpectEvent(0, 1, 0, context.MatchResult.Matches[0].Package); } TEST_CASE("ARPChanges_NoChange_MultiMatch", "[ARPChanges][workflow]") @@ -340,7 +341,7 @@ TEST_CASE("ARPChanges_SingleChange_SingleMatch", "[ARPChanges][workflow]") context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); context << ReportARPChanges; - context.ExpectEvent(1, 1, 0, context.MatchResult.Matches.back().Package.get()); + context.ExpectEvent(1, 1, 0, context.MatchResult.Matches.back().Package); } TEST_CASE("ARPChanges_SingleChange_MultiMatch", "[ARPChanges][workflow]") @@ -356,7 +357,7 @@ TEST_CASE("ARPChanges_SingleChange_MultiMatch", "[ARPChanges][workflow]") context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); context << ReportARPChanges; - context.ExpectEvent(1, 2, 1, context.EverythingResult.Matches.back().Package.get()); + context.ExpectEvent(1, 2, 1, context.EverythingResult.Matches.back().Package); } TEST_CASE("ARPChanges_MultiChange_NoMatch", "[ARPChanges][workflow]") @@ -387,7 +388,7 @@ TEST_CASE("ARPChanges_MultiChange_SingleMatch_NoOverlap", "[ARPChanges][workflow context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); context << ReportARPChanges; - context.ExpectEvent(2, 1, 0, context.MatchResult.Matches.back().Package.get()); + context.ExpectEvent(2, 1, 0, context.MatchResult.Matches.back().Package); } TEST_CASE("ARPChanges_MultiChange_SingleMatch_Overlap", "[ARPChanges][workflow]") @@ -403,7 +404,7 @@ TEST_CASE("ARPChanges_MultiChange_SingleMatch_Overlap", "[ARPChanges][workflow]" context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); context << ReportARPChanges; - context.ExpectEvent(2, 1, 1, context.MatchResult.Matches.back().Package.get()); + context.ExpectEvent(2, 1, 1, context.MatchResult.Matches.back().Package); } TEST_CASE("ARPChanges_MultiChange_MultiMatch_NoOverlap", "[ARPChanges][workflow]") @@ -437,7 +438,7 @@ TEST_CASE("ARPChanges_MultiChange_MultiMatch_SingleOverlap", "[ARPChanges][workf context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); context << ReportARPChanges; - context.ExpectEvent(2, 2, 1, context.MatchResult.Matches.back().Package.get()); + context.ExpectEvent(2, 2, 1, context.MatchResult.Matches.back().Package); } TEST_CASE("ARPChanges_MultiChange_MultiMatch_MultiOverlap", "[ARPChanges][workflow]") diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp @@ -10,6 +10,7 @@ #include <PackageTrackingCatalogSourceFactory.h> #include <winget/Pin.h> #include <winget/PinningData.h> +#include <winget/PackageVersionSelection.h> using namespace std::string_literals; using namespace std::string_view_literals; @@ -159,17 +160,17 @@ struct TestPackageHelper return *this; } - operator std::shared_ptr<IPackage>() + operator std::shared_ptr<ICompositePackage>() { if (!m_package) { if (m_isInstalled) { - m_package = TestPackage::Make(m_manifest, TestPackage::MetadataMap{}, std::vector<Manifest::Manifest>(), m_source); + m_package = TestCompositePackage::Make(m_manifest, TestCompositePackage::MetadataMap{}, std::vector<Manifest::Manifest>(), m_source); } else { - m_package = TestPackage::Make(std::vector<Manifest::Manifest>{ m_manifest }, m_source, m_hideSystemReferenceStrings); + m_package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ m_manifest }, m_source, m_hideSystemReferenceStrings); } } @@ -185,7 +186,7 @@ private: bool m_isInstalled; Manifest::Manifest m_manifest; std::shared_ptr<ISource> m_source; - std::shared_ptr<TestPackage> m_package; + std::shared_ptr<TestCompositePackage> m_package; bool m_hideSystemReferenceStrings = false; }; @@ -231,8 +232,8 @@ TEST_CASE("CompositeSource_PackageFamilyName_NotAvailable", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().empty()); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); } TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") @@ -253,8 +254,9 @@ TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); } TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") @@ -267,8 +269,8 @@ TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().empty()); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); } TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") @@ -289,8 +291,9 @@ TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); } TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") @@ -309,8 +312,9 @@ TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); } TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") @@ -330,10 +334,12 @@ TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name).get() == name); - REQUIRE(!Version(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Version)).IsUnknown()); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); + auto version = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); + REQUIRE(version->GetProperty(PackageVersionProperty::Name).get() == name); + REQUIRE(!Version(version->GetProperty(PackageVersionProperty::Version)).IsUnknown()); } TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource]") @@ -351,8 +357,8 @@ TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 0); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); } TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") @@ -386,8 +392,9 @@ TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); } TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") @@ -417,8 +424,9 @@ TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); } TEST_CASE("CompositeSource_FoundByAvailableRootSearch_NotInstalled", "[CompositeSource]") @@ -465,8 +473,9 @@ TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); REQUIRE(result.Matches[0].MatchCriteria.Type == originalType); // Now make the source root search find it with a better criteria @@ -484,8 +493,9 @@ TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); REQUIRE(result.Matches[0].MatchCriteria.Type == type); } @@ -539,19 +549,27 @@ TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSo hasChannel.Version = "2.0"; SearchResult result; - result.Matches.emplace_back(TestPackage::Make(std::vector<Manifest::Manifest>{ noChannel, hasChannel }, setup.Available), Criteria()); - REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys().size() == 2); + result.Matches.emplace_back(TestCompositePackage::Make(std::vector<Manifest::Manifest>{ noChannel, hasChannel }, setup.Available), Criteria()); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2); return result; }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(); - REQUIRE(versionKeys.size() == 1); - REQUIRE(versionKeys[0].Channel.empty()); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + auto package = result.Matches[0].Package->GetAvailable()[0]; - auto latestVersion = result.Matches[0].Package->GetLatestAvailableVersion(); + auto versionKeys = package->GetVersionKeys(); + REQUIRE(versionKeys.size() == 2); + + auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package); + auto availableVersionKeys = availableVersions->GetVersionKeys(); + REQUIRE(availableVersionKeys.size() == 1); + REQUIRE(availableVersionKeys[0].Channel.empty()); + + auto latestVersion = availableVersions->GetLatestVersion(); REQUIRE(latestVersion); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get().empty()); } @@ -573,19 +591,27 @@ TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[Composite hasChannel.Version = "2.0"; SearchResult result; - result.Matches.emplace_back(TestPackage::Make(std::vector<Manifest::Manifest>{ noChannel, hasChannel }, setup.Available), Criteria()); - REQUIRE(result.Matches.back().Package->GetAvailableVersionKeys().size() == 2); + result.Matches.emplace_back(TestCompositePackage::Make(std::vector<Manifest::Manifest>{ noChannel, hasChannel }, setup.Available), Criteria()); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2); return result; }; SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - auto versionKeys = result.Matches[0].Package->GetAvailableVersionKeys(); - REQUIRE(versionKeys.size() == 1); - REQUIRE(versionKeys[0].Channel == channel); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + auto package = result.Matches[0].Package->GetAvailable()[0]; + + auto versionKeys = package->GetVersionKeys(); + REQUIRE(versionKeys.size() == 2); - auto latestVersion = result.Matches[0].Package->GetLatestAvailableVersion(); + auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package); + auto availableVersionKeys = availableVersions->GetVersionKeys(); + REQUIRE(availableVersionKeys.size() == 1); + REQUIRE(availableVersionKeys[0].Channel == channel); + + auto latestVersion = availableVersions->GetLatestVersion(); REQUIRE(latestVersion); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get() == channel); } @@ -625,9 +651,10 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchAll", "[CompositeSource SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 2); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name).get() == firstName); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 2); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == firstName); + REQUIRE(result.Matches[0].Package->GetAvailable()[1]->GetProperty(PackageProperty::Name).get() == secondName); } TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSource]") @@ -654,9 +681,9 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSou SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name).get() == secondName); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == secondName); } TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[CompositeSource]") @@ -684,15 +711,15 @@ TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[Composi SearchResult result = setup.Search(); REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetAvailableVersionKeys().size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); } TEST_CASE("CompositeSource_IsSame", "[CompositeSource]") { CompositeTestSetup setup; setup.Installed->Everything.Matches.emplace_back(MakeInstalled().WithPFN("sortof_apfn"), Criteria()); - setup.Available->Everything.Matches.emplace_back(MakeAvailable(setup.Available).WithPFN("sortof_apfn"), Criteria()); SearchResult result1 = setup.Search(); REQUIRE(result1.Matches.size() == 1); @@ -700,7 +727,8 @@ TEST_CASE("CompositeSource_IsSame", "[CompositeSource]") SearchResult result2 = setup.Search(); REQUIRE(result2.Matches.size() == 1); - REQUIRE(result1.Matches[0].Package->IsSame(result2.Matches[0].Package.get())); + REQUIRE(result1.Matches[0].Package->GetInstalled()); + REQUIRE(result1.Matches[0].Package->GetInstalled()->IsSame(result1.Matches[0].Package->GetInstalled().get())); } TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]") @@ -727,8 +755,9 @@ TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]") SearchResult result = Composite.Search({}); REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - auto pfns = result.Matches[0].Package->GetLatestAvailableVersion()->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + auto pfns = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); REQUIRE(pfns.size() == 1); REQUIRE(pfns[0] == pfn); @@ -872,9 +901,11 @@ TEST_CASE("CompositeSource_TrackingPackageFound", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()); } TEST_CASE("CompositeSource_TrackingPackageFound_MetadataPopulatedFromTracking", "[CompositeSource]") @@ -928,9 +959,9 @@ TEST_CASE("CompositeSource_TrackingPackageFound_MetadataPopulatedFromTracking", REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - auto metadata = result.Matches[0].Package->GetInstalledVersion()->GetMetadata(); + auto metadata = GetInstalledVersion(result.Matches[0].Package)->GetMetadata(); REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentArchitecture] == "X86"); REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentLocale] == "en-US"); REQUIRE(metadata[Repository::PackageVersionMetadata::InstalledArchitecture] == "X86"); @@ -963,9 +994,10 @@ TEST_CASE("CompositeSource_TrackingFound_AvailableNot", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(!result.Matches[0].Package->GetLatestAvailableVersion()); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); } TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]") @@ -1004,9 +1036,11 @@ TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]") REQUIRE(result.Matches.size() == 1); REQUIRE(result.Matches[0].Package); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()); - REQUIRE(result.Matches[0].Package->GetInstalledVersion()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(result.Matches[0].Package->GetLatestAvailableVersion()); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()); } TEST_CASE("CompositeSource_TrackingFound_NotInstalled", "[CompositeSource]") @@ -1043,8 +1077,7 @@ TEST_CASE("CompositeSource_NullAvailableVersion", "[CompositeSource]") setup.Available->Everything.Matches.emplace_back(MakeInstalled(), Criteria()); // We are mostly testing to see if a null available version causes an AV or not - SearchResult result = setup.Search(); - REQUIRE(result.Matches.size() == 1); + REQUIRE_THROWS_HR(setup.Search(), E_UNEXPECTED); } struct ExpectedResultForPinBehavior @@ -1071,17 +1104,18 @@ struct ExpectedResultsForPinning std::vector<ExpectedPackageVersionKey> AvailableVersions; }; -void RequireExpectedResultsWithPin(std::shared_ptr<IPackage> package, const ExpectedResultsForPinning& expectedResult) +void RequireExpectedResultsWithPin(std::shared_ptr<ICompositePackage> package, const ExpectedResultsForPinning& expectedResult) { PinningData pinningData{ PinningData::Disposition::ReadOnly }; + auto availableVersions = GetAvailableVersionsForInstalledVersion(package); for (const auto& entry : expectedResult.ResultsForPinBehavior) { auto pinBehavior = entry.first; const auto& result = entry.second; - auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, package->GetInstalledVersion()); - auto latestAvailable = evaluator.GetLatestAvailableVersionForPins(package); + auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, GetInstalledVersion(package)); + auto latestAvailable = evaluator.GetLatestAvailableVersionForPins(availableVersions); REQUIRE(evaluator.IsUpdate(latestAvailable) == result.IsUpdateAvailable); @@ -1096,13 +1130,13 @@ void RequireExpectedResultsWithPin(std::shared_ptr<IPackage> package, const Expe } } - auto availableVersionKeys = package->GetAvailableVersionKeys(); + auto availableVersionKeys = availableVersions->GetVersionKeys(); REQUIRE(availableVersionKeys.size() == expectedResult.AvailableVersions.size()); for (size_t i = 0; i < availableVersionKeys.size(); ++i) { - auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, package->GetInstalledVersion()); + auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, GetInstalledVersion(package)); - auto packageVersion = package->GetAvailableVersion(expectedResult.AvailableVersions[i]); + auto packageVersion = availableVersions->GetVersion(expectedResult.AvailableVersions[i]); REQUIRE(packageVersion); REQUIRE(availableVersionKeys[i].SourceId == expectedResult.AvailableVersions[i].SourceId); REQUIRE(availableVersionKeys[i].Version == expectedResult.AvailableVersions[i].Version); @@ -1122,7 +1156,7 @@ TEST_CASE("CompositeSource_Pinning_AvailableVersionPinned", "[CompositeSource][P TestUserSettings userSettings; CompositeTestSetup setup; - auto installedPackage = TestPackage::Make(MakeDefaultManifest("1.0.1"sv), TestPackage::MetadataMap{}); + auto installedPackage = TestCompositePackage::Make(MakeDefaultManifest("1.0.1"sv), TestCompositePackage::MetadataMap{}); setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Available->SearchFunction = [&](const SearchRequest&) @@ -1130,7 +1164,7 @@ TEST_CASE("CompositeSource_Pinning_AvailableVersionPinned", "[CompositeSource][P auto manifest1 = MakeDefaultManifest("1.0.0"sv); auto manifest2 = MakeDefaultManifest("1.0.1"sv); auto manifest3 = MakeDefaultManifest("1.1.0"sv); - auto package = TestPackage::Make( + auto package = TestCompositePackage::Make( std::vector<Manifest::Manifest>{ manifest3, manifest2, manifest1 }, setup.Available); @@ -1233,12 +1267,12 @@ TEST_CASE("CompositeSource_Pinning_OneSourcePinned", "[CompositeSource][PinFlow] TestUserSettings userSettings; CompositeTestSetup setup; - auto installedPackage = TestPackage::Make(MakeDefaultManifest("1.0"sv), TestPackage::MetadataMap{}); + auto installedPackage = TestCompositePackage::Make(MakeDefaultManifest("1.0"sv), TestCompositePackage::MetadataMap{}); setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Available->SearchFunction = [&](const SearchRequest&) { - auto package = TestPackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("2.0"sv) }, setup.Available); + auto package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("2.0"sv) }, setup.Available); SearchResult result; result.Matches.emplace_back(package, Criteria()); @@ -1249,7 +1283,7 @@ TEST_CASE("CompositeSource_Pinning_OneSourcePinned", "[CompositeSource][PinFlow] setup.Composite.AddAvailableSource(Source{ secondAvailable }); secondAvailable->SearchFunction = [&](const SearchRequest&) { - auto package = TestPackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("1.1"sv) }, secondAvailable); + auto package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("1.1"sv) }, secondAvailable); SearchResult result; result.Matches.emplace_back(package, Criteria()); @@ -1289,12 +1323,12 @@ TEST_CASE("CompositeSource_Pinning_OneSourceGated", "[CompositeSource][PinFlow]" TestUserSettings userSettings; CompositeTestSetup setup; - auto installedPackage = TestPackage::Make(MakeDefaultManifest("1.0"sv), TestPackage::MetadataMap{}); + auto installedPackage = TestCompositePackage::Make(MakeDefaultManifest("1.0"sv), TestCompositePackage::MetadataMap{}); setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); setup.Available->SearchFunction = [&](const SearchRequest&) { - auto package = TestPackage::Make( + auto package = TestCompositePackage::Make( std::vector<Manifest::Manifest>{ MakeDefaultManifest("2.0"sv), MakeDefaultManifest("1.2"sv), @@ -1310,7 +1344,7 @@ TEST_CASE("CompositeSource_Pinning_OneSourceGated", "[CompositeSource][PinFlow]" setup.Composite.AddAvailableSource(Source{ secondAvailable }); secondAvailable->SearchFunction = [&](const SearchRequest&) { - auto package = TestPackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("1.1"sv) }, secondAvailable); + auto package = TestCompositePackage::Make(std::vector<Manifest::Manifest>{ MakeDefaultManifest("1.1"sv) }, secondAvailable); SearchResult result; result.Matches.emplace_back(package, Criteria()); diff --git a/src/AppInstallerCLITests/DependenciesTestSource.h b/src/AppInstallerCLITests/DependenciesTestSource.h @@ -188,9 +188,9 @@ namespace TestCommon //auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); result.Matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest }, shared_from_this() ), @@ -200,7 +200,7 @@ namespace TestCommon { result.Matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( std::vector<Manifest>{ manifest }, shared_from_this() ), diff --git a/src/AppInstallerCLITests/PackageTrackingCatalog.cpp b/src/AppInstallerCLITests/PackageTrackingCatalog.cpp @@ -86,8 +86,9 @@ TEST_CASE("TrackingCatalog_Install", "[tracking_catalog]") SearchResult resultAfter = catalog.Search(request); REQUIRE(resultAfter.Matches.size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); - auto trackingVersion = resultAfter.Matches[0].Package->GetLatestAvailableVersion(); + auto trackingVersion = resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); REQUIRE(trackingVersion); auto metadata = trackingVersion->GetMetadata(); @@ -113,7 +114,8 @@ TEST_CASE("TrackingCatalog_Reinstall", "[tracking_catalog]") SearchResult resultBefore = catalog.Search(request); REQUIRE(resultBefore.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name) == + REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Name) == manifest.DefaultLocalization.Get<Localization::PackageName>()); // Change name @@ -124,7 +126,8 @@ TEST_CASE("TrackingCatalog_Reinstall", "[tracking_catalog]") SearchResult resultAfter = catalog.Search(request); REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Name) == + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Name) == newName); } @@ -147,17 +150,19 @@ TEST_CASE("TrackingCatalog_Upgrade", "[tracking_catalog]") SearchResult resultBefore = catalog.Search(request); REQUIRE(resultBefore.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Version) == + REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == manifest.Version); - // Change name + // Change version manifest.Version = "99.1.2.3"; catalog.RecordInstall(manifest, manifest.Installers[0], true); SearchResult resultAfter = catalog.Search(request); REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetLatestAvailableVersion()->GetProperty(PackageVersionProperty::Version) == + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == manifest.Version); } diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -86,7 +86,7 @@ TEST_CASE("SQLiteIndexSource_Id", "[sqliteindexsource]") auto results = source->Search(request); REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); - auto latestVersion = results.Matches[0].Package->GetLatestAvailableVersion(); + auto latestVersion = results.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Id).get() == manifest.Id); } @@ -107,7 +107,7 @@ TEST_CASE("SQLiteIndexSource_Name", "[sqliteindexsource]") auto results = source->Search(request); REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); - auto latestVersion = results.Matches[0].Package->GetLatestAvailableVersion(); + auto latestVersion = results.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Name).get() == manifest.DefaultLocalization.Get<Localization::PackageName>()); } @@ -128,8 +128,9 @@ TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") auto results = source->Search(request); REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); + REQUIRE(results.Matches[0].Package->GetAvailable().size() == 1); - auto result = results.Matches[0].Package->GetAvailableVersionKeys(); + auto result = results.Matches[0].Package->GetAvailable()[0]->GetVersionKeys(); REQUIRE(result.size() == 1); REQUIRE(result[0].Version == manifest.Version); REQUIRE(result[0].Channel == manifest.Channel); @@ -151,9 +152,10 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") auto results = source->Search(request); REQUIRE(results.Matches.size() == 1); REQUIRE(results.Matches[0].Package); - auto package = results.Matches[0].Package.get(); + REQUIRE(results.Matches[0].Package->GetAvailable().size() == 1); + auto package = results.Matches[0].Package->GetAvailable()[0]; - auto specificResultVersion = package->GetAvailableVersion(PackageVersionKey("", manifest.Version, manifest.Channel)); + auto specificResultVersion = package->GetVersion(PackageVersionKey("", manifest.Version, manifest.Channel)); REQUIRE(specificResultVersion); auto specificResult = specificResultVersion->GetManifest(); REQUIRE(specificResult.Id == manifest.Id); @@ -161,7 +163,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(specificResult.Version == manifest.Version); REQUIRE(specificResult.Channel == manifest.Channel); - auto latestResultVersion = package->GetAvailableVersion(PackageVersionKey("", "", manifest.Channel)); + auto latestResultVersion = package->GetVersion(PackageVersionKey("", "", manifest.Channel)); REQUIRE(latestResultVersion); auto latestResult = latestResultVersion->GetManifest(); REQUIRE(latestResult.Id == manifest.Id); @@ -169,7 +171,7 @@ TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") REQUIRE(latestResult.Version == manifest.Version); REQUIRE(latestResult.Channel == manifest.Channel); - auto noResultVersion = package->GetAvailableVersion(PackageVersionKey("", "blargle", "flargle")); + auto noResultVersion = package->GetVersion(PackageVersionKey("", "blargle", "flargle")); REQUIRE(!noResultVersion); } @@ -188,9 +190,11 @@ TEST_CASE("SQLiteIndexSource_IsSame", "[sqliteindexsource]") auto result1 = source->Search(request); REQUIRE(result1.Matches.size() == 1); + REQUIRE(result1.Matches[0].Package->GetAvailable().size() == 1); auto result2 = source->Search(request); REQUIRE(result2.Matches.size() == 1); + REQUIRE(result2.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result1.Matches[0].Package->IsSame(result2.Matches[0].Package.get())); + REQUIRE(result1.Matches[0].Package->GetAvailable()[0]->IsSame(result2.Matches[0].Package->GetAvailable()[0].get())); } diff --git a/src/AppInstallerCLITests/Sources.cpp b/src/AppInstallerCLITests/Sources.cpp @@ -195,9 +195,9 @@ namespace PackageMatchFilter testMatchFilter1{ PackageMatchField::Id, MatchType::Exact, "test" }; PackageMatchFilter testMatchFilter2{ PackageMatchField::Name, MatchType::Exact, "test" }; PackageMatchFilter testMatchFilter3{ PackageMatchField::Id, MatchType::CaseInsensitive, "test" }; - result.Matches.emplace_back(std::shared_ptr<IPackage>(), testMatchFilter1); - result.Matches.emplace_back(std::shared_ptr<IPackage>(), testMatchFilter2); - result.Matches.emplace_back(std::shared_ptr<IPackage>(), testMatchFilter3); + result.Matches.emplace_back(nullptr, testMatchFilter1); + result.Matches.emplace_back(nullptr, testMatchFilter2); + result.Matches.emplace_back(nullptr, testMatchFilter3); return result; } }; diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp @@ -117,34 +117,28 @@ namespace TestCommon } } - TestPackage::TestPackage(const std::vector<Manifest>& available, std::weak_ptr<const ISource> source, bool hideSystemReferenceStringsOnVersion) + TestPackage::TestPackage(const std::vector<Manifest>& available, std::weak_ptr<const ISource> source, bool hideSystemReferenceStringsOnVersion) : + Source(source) { for (const auto& manifest : available) { - AvailableVersions.emplace_back(TestPackageVersion::Make(manifest, source, hideSystemReferenceStringsOnVersion)); + Versions.emplace_back(TestPackageVersion::Make(manifest, source, hideSystemReferenceStringsOnVersion)); } } - TestPackage::TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector<Manifest>& available, std::weak_ptr<const ISource> source) : - InstalledVersion(TestPackageVersion::Make(installed, std::move(installationMetadata), source)) + TestPackage::TestPackage(const Manifest& installed, MetadataMap installationMetadata, std::weak_ptr<const ISource> source) : + Source(source) { - for (const auto& manifest : available) - { - AvailableVersions.emplace_back(TestPackageVersion::Make(manifest, source)); - } + Versions.emplace_back(TestPackageVersion::Make(installed, std::move(installationMetadata), source)); } TestPackage::LocIndString TestPackage::GetProperty(PackageProperty property) const { std::shared_ptr<IPackageVersion> truth; - if (!AvailableVersions.empty()) + if (!Versions.empty()) { - truth = AvailableVersions[0]; - } - else - { - truth = InstalledVersion; + truth = Versions[0]; } if (!truth) @@ -163,34 +157,29 @@ namespace TestCommon } } - std::shared_ptr<IPackageVersion> TestPackage::GetInstalledVersion() const - { - return InstalledVersion; - } - - std::vector<PackageVersionKey> TestPackage::GetAvailableVersionKeys() const + std::vector<PackageVersionKey> TestPackage::GetVersionKeys() const { std::vector<PackageVersionKey> result; - for (const auto& version : AvailableVersions) + for (const auto& version : Versions) { result.emplace_back(PackageVersionKey(version->GetSource().GetIdentifier(), version->GetProperty(PackageVersionProperty::Version).get(), version->GetProperty(PackageVersionProperty::Channel).get())); } return result; } - std::shared_ptr<IPackageVersion> TestPackage::GetLatestAvailableVersion() const + std::shared_ptr<IPackageVersion> TestPackage::GetLatestVersion() const { - if (AvailableVersions.empty()) + if (Versions.empty()) { return {}; } - return AvailableVersions[0]; + return Versions[0]; } - std::shared_ptr<IPackageVersion> TestPackage::GetAvailableVersion(const PackageVersionKey& versionKey) const + std::shared_ptr<IPackageVersion> TestPackage::GetVersion(const PackageVersionKey& versionKey) const { - for (const auto& version : AvailableVersions) + for (const auto& version : Versions) { if ((versionKey.Version.empty() || versionKey.Version == version->GetProperty(PackageVersionProperty::Version).get()) && (versionKey.Channel.empty() || versionKey.Channel == version->GetProperty(PackageVersionProperty::Channel).get())) @@ -202,6 +191,11 @@ namespace TestCommon return {}; } + Repository::Source TestPackage::GetSource() const + { + return std::const_pointer_cast<TestPackage::ISource>(Source.lock()); + } + bool TestPackage::IsSame(const IPackage* other) const { if (IsSameOverride) @@ -212,15 +206,14 @@ namespace TestCommon const TestPackage* otherAvailable = PackageCast<const TestPackage*>(other); if (!otherAvailable || - InstalledVersion.get() != otherAvailable->InstalledVersion.get() || - AvailableVersions.size() != otherAvailable->AvailableVersions.size()) + Versions.size() != otherAvailable->Versions.size()) { return false; } - for (size_t i = 0; i < AvailableVersions.size(); ++i) + for (size_t i = 0; i < Versions.size(); ++i) { - if (AvailableVersions[i].get() != otherAvailable->AvailableVersions[i].get()) + if (Versions[i].get() != otherAvailable->Versions[i].get()) { return false; } @@ -239,6 +232,62 @@ namespace TestCommon return nullptr; } + TestCompositePackage::TestCompositePackage(const std::vector<Manifest>& available, std::weak_ptr<const ISource> source, bool hideSystemReferenceStringsOnVersion) + { + if (!available.empty()) + { + Available.emplace_back(TestPackage::Make(available, source, hideSystemReferenceStringsOnVersion)); + } + } + + TestCompositePackage::TestCompositePackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector<Manifest>& available, std::weak_ptr<const ISource> source) : + Installed(TestPackage::Make(installed, std::move(installationMetadata), source)) + { + if (!available.empty()) + { + Available.emplace_back(TestPackage::Make(available, source)); + } + } + + TestCompositePackage::LocIndString TestCompositePackage::GetProperty(PackageProperty property) const + { + std::shared_ptr<IPackage> truth; + + if (!Available.empty()) + { + truth = Available[0]; + } + else + { + truth = Installed; + } + + if (!truth) + { + THROW_HR(E_NOT_VALID_STATE); + } + + switch (property) + { + case PackageProperty::Id: + return truth->GetProperty(PackageProperty::Id); + case PackageProperty::Name: + return truth->GetProperty(PackageProperty::Name); + default: + return {}; + } + } + + std::shared_ptr<AppInstaller::Repository::IPackage> TestCompositePackage::GetInstalled() + { + return Installed; + } + + std::vector<std::shared_ptr<AppInstaller::Repository::IPackage>> TestCompositePackage::GetAvailable() + { + return { Available.begin(), Available.end() }; + } + const SourceDetails& TestSource::GetDetails() const { return Details; diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h @@ -56,7 +56,7 @@ namespace TestCommon TestPackage(const std::vector<Manifest>& available, std::weak_ptr<const ISource> source = {}, bool hideSystemReferenceStringsOnVersion = false); // Create a package with an installed version, metadata, and optionally available versions. - TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector<Manifest>& available = {}, std::weak_ptr<const ISource> source = {}); + TestPackage(const Manifest& installed, MetadataMap installationMetadata, std::weak_ptr<const ISource> source = {}); template <typename... Args> static std::shared_ptr<TestPackage> Make(Args&&... args) @@ -65,18 +65,46 @@ namespace TestCommon } AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; - std::shared_ptr<AppInstaller::Repository::IPackageVersion> GetInstalledVersion() const override; - std::vector<AppInstaller::Repository::PackageVersionKey> GetAvailableVersionKeys() const override; - std::shared_ptr<AppInstaller::Repository::IPackageVersion> GetLatestAvailableVersion() const override; - std::shared_ptr<AppInstaller::Repository::IPackageVersion> GetAvailableVersion(const AppInstaller::Repository::PackageVersionKey& versionKey) const override; + std::vector<AppInstaller::Repository::PackageVersionKey> GetVersionKeys() const override; + std::shared_ptr<AppInstaller::Repository::IPackageVersion> GetLatestVersion() const override; + std::shared_ptr<AppInstaller::Repository::IPackageVersion> GetVersion(const AppInstaller::Repository::PackageVersionKey& versionKey) const override; + AppInstaller::Repository::Source GetSource() const override; bool IsSame(const IPackage* other) const override; const void* CastTo(AppInstaller::Repository::IPackageType type) const override; - std::shared_ptr<AppInstaller::Repository::IPackageVersion> InstalledVersion; - std::vector<std::shared_ptr<AppInstaller::Repository::IPackageVersion>> AvailableVersions; + std::vector<std::shared_ptr<AppInstaller::Repository::IPackageVersion>> Versions; + std::weak_ptr<const ISource> Source; std::function<bool(const IPackage*, const IPackage*)> IsSameOverride; }; + // ICompositePackage for TestSource + struct TestCompositePackage : public AppInstaller::Repository::ICompositePackage + { + using Manifest = AppInstaller::Manifest::Manifest; + using ISource = AppInstaller::Repository::ISource; + using LocIndString = AppInstaller::Utility::LocIndString; + using MetadataMap = TestPackageVersion::MetadataMap; + + // Create a package with only available versions using these manifests. + TestCompositePackage(const std::vector<Manifest>& available, std::weak_ptr<const ISource> source = {}, bool hideSystemReferenceStringsOnVersion = false); + + // Create a package with an installed version, metadata, and optionally available versions. + TestCompositePackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector<Manifest>& available = {}, std::weak_ptr<const ISource> source = {}); + + template <typename... Args> + static std::shared_ptr<TestCompositePackage> Make(Args&&... args) + { + return std::make_shared<TestCompositePackage>(std::forward<Args>(args)...); + } + + AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; + std::shared_ptr<AppInstaller::Repository::IPackage> GetInstalled() override; + std::vector<std::shared_ptr<AppInstaller::Repository::IPackage>> GetAvailable() override; + + std::shared_ptr<TestPackage> Installed; + std::vector<std::shared_ptr<TestPackage>> Available; + }; + // An ISource implementation for use across the test code. struct TestSource : public AppInstaller::Repository::ISource, public std::enable_shared_from_this<TestSource> { diff --git a/src/AppInstallerCLITests/WorkflowCommon.cpp b/src/AppInstallerCLITests/WorkflowCommon.cpp @@ -27,7 +27,7 @@ namespace TestCommon auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector<Manifest>{ manifest }, source), + TestCompositePackage::Make(std::vector<Manifest>{ manifest }, source), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnOne"))); }); @@ -37,13 +37,13 @@ namespace TestCommon auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector<Manifest>{ manifest }, source), + TestCompositePackage::Make(std::vector<Manifest>{ manifest }, source), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); auto manifest2 = YamlParser::CreateFromPath(TestDataFile("Manifest-Good.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector<Manifest>{ manifest2 }, source), + TestCompositePackage::Make(std::vector<Manifest>{ manifest2 }, source), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); }); @@ -55,9 +55,9 @@ namespace TestCommon auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); auto testPackage = - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap + TestCompositePackage::MetadataMap { { PackageVersionMetadata::InstalledType, "Exe" }, { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, @@ -66,7 +66,10 @@ namespace TestCommon std::vector<Manifest>{ manifest3, manifest2, manifest }, source ); - testPackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + for (auto& availablePackage : testPackage->Available) + { + availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + } matches.emplace_back( ResultMatch( testPackage, @@ -81,9 +84,9 @@ namespace TestCommon auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2_LicenseAgreement.yaml")); auto testPackage = - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap + TestCompositePackage::MetadataMap { { PackageVersionMetadata::InstalledType, "Exe" }, { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, @@ -92,7 +95,10 @@ namespace TestCommon std::vector<Manifest>{ manifest3, manifest2, manifest }, source ); - testPackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + for (auto& availablePackage : testPackage->Available) + { + availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + } matches.emplace_back( ResultMatch( testPackage, @@ -106,9 +112,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Portable.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Portable" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Portable" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -123,9 +129,9 @@ namespace TestCommon matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -140,9 +146,9 @@ namespace TestCommon matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -157,9 +163,9 @@ namespace TestCommon matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap + TestCompositePackage::MetadataMap { { PackageVersionMetadata::InstalledType, "Msix" }, { PackageVersionMetadata::PinnedState, "PinnedByManifest" }, @@ -177,9 +183,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Zip_Exe.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -195,9 +201,9 @@ namespace TestCommon installed.Version = "1.0.0.0"; matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( installed, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "MSStore" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "MSStore" } }, std::vector<Manifest>{ available }, source ), @@ -211,9 +217,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_ExpectedReturnCodes.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -229,9 +235,9 @@ namespace TestCommon installed.Version = "unknown"; matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( installed, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ available }, source ), @@ -245,9 +251,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest2, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -261,9 +267,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -277,9 +283,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_ARPInstallerType.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -293,9 +299,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_UnsupportedArgs.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -308,7 +314,7 @@ namespace TestCommon auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( std::vector<Manifest>{ manifest }, source ), @@ -322,9 +328,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_ExeDependencies.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap + TestCompositePackage::MetadataMap { { PackageVersionMetadata::InstalledType, "Exe" }, { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, @@ -342,7 +348,7 @@ namespace TestCommon auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Msix_WFDependency.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( std::vector<Manifest>{ manifest }, source ), @@ -356,9 +362,9 @@ namespace TestCommon auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2_LicenseAgreement.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest }, source ), @@ -373,9 +379,9 @@ namespace TestCommon auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest, - TestPackage::MetadataMap + TestCompositePackage::MetadataMap { { PackageVersionMetadata::InstalledType, "Exe" }, { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, @@ -387,9 +393,9 @@ namespace TestCommon PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); matches.emplace_back( ResultMatch( - TestPackage::Make( + TestCompositePackage::Make( manifest2, - TestPackage::MetadataMap + TestCompositePackage::MetadataMap { { PackageVersionMetadata::InstalledType, "Exe" }, { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, diff --git a/src/AppInstallerRepositoryCore/ARPCorrelation.cpp b/src/AppInstallerRepositoryCore/ARPCorrelation.cpp @@ -89,7 +89,7 @@ namespace AppInstaller::Repository::Correlation auto score = algorithm.ComputeConfidence(arpEntry); AICLI_LOG(Repo, Verbose, << "Match confidence for " << arpEntry.Entry->GetProperty(PackageProperty::Id) << ": " << score); - result.Measures.emplace_back(CorrelationMeasure{ score, arpEntry.Entry->GetInstalledVersion() }); + result.Measures.emplace_back(CorrelationMeasure{ score, arpEntry.Entry->GetLatestVersion() }); } std::sort(result.Measures.begin(), result.Measures.end(), [](const CorrelationMeasure& a, const CorrelationMeasure& b) { return a.Measure > b.Measure; }); @@ -122,11 +122,11 @@ namespace AppInstaller::Repository::Correlation for (const auto& entry : preInstallARP.Search({}).Matches) { - auto installed = entry.Package->GetInstalledVersion(); + auto installed = entry.Package->GetInstalled()->GetLatestVersion(); if (installed) { m_preInstallSnapshot.emplace_back(std::make_tuple( - entry.Package->GetProperty(PackageProperty::Id), + installed->GetProperty(PackageVersionProperty::Id), installed->GetProperty(PackageVersionProperty::Version), installed->GetProperty(PackageVersionProperty::Channel))); } @@ -143,17 +143,17 @@ namespace AppInstaller::Repository::Correlation for (auto& entry : m_postInstallSnapshotSource.Search({}).Matches) { - auto installed = entry.Package->GetInstalledVersion(); + auto installed = entry.Package->GetInstalled()->GetLatestVersion(); if (installed) { auto entryKey = std::make_tuple( - entry.Package->GetProperty(PackageProperty::Id), + installed->GetProperty(PackageVersionProperty::Id), installed->GetProperty(PackageVersionProperty::Version), installed->GetProperty(PackageVersionProperty::Channel)); auto itr = std::lower_bound(m_preInstallSnapshot.begin(), m_preInstallSnapshot.end(), entryKey); - m_postInstallSnapshot.emplace_back(entry.Package, itr == m_preInstallSnapshot.end() || *itr != entryKey); + m_postInstallSnapshot.emplace_back(entry.Package->GetInstalled(), itr == m_preInstallSnapshot.end() || *itr != entryKey); } } } @@ -239,7 +239,7 @@ namespace AppInstaller::Repository::Correlation { for (const auto& byManifest : findByManifest.Matches) { - if (change.Entry->IsSame(byManifest.Package.get())) + if (change.Entry->IsSame(byManifest.Package->GetInstalled().get())) { packagesInBoth.emplace_back(change.Entry); break; @@ -266,18 +266,18 @@ namespace AppInstaller::Repository::Correlation // If there is only a single common package (changed and matches), it is almost certainly the correct one. if (settings.AllowNormalization && packagesInBoth.size() == 1) { - result.Package = packagesInBoth[0]->GetInstalledVersion(); + result.Package = packagesInBoth[0]->GetLatestVersion(); result.Reason = "normalization match and new/changed"; } // If it wasn't changed but we still find a match, that is the best thing to report. else if (settings.AllowNormalization && findByManifest.Matches.size() == 1) { - result.Package = findByManifest.Matches[0].Package->GetInstalledVersion(); + result.Package = findByManifest.Matches[0].Package->GetInstalled()->GetLatestVersion(); result.Reason = "normalization match (not new/changed)"; } else if (settings.AllowSingleChange && result.ChangesToARP == 1) { - result.Package = std::find_if(m_postInstallSnapshot.begin(), m_postInstallSnapshot.end(), [](const ARPEntry& e) { return e.IsNewOrUpdated; })->Entry->GetInstalledVersion(); + result.Package = std::find_if(m_postInstallSnapshot.begin(), m_postInstallSnapshot.end(), [](const ARPEntry& e) { return e.IsNewOrUpdated; })->Entry->GetLatestVersion(); result.Reason = "only new/changed value"; } else diff --git a/src/AppInstallerRepositoryCore/ARPCorrelationAlgorithms.cpp b/src/AppInstallerRepositoryCore/ARPCorrelationAlgorithms.cpp @@ -145,8 +145,8 @@ namespace AppInstaller::Repository::Correlation { // Name and Publisher are available as multi properties, but for ARP entries there will only be 0 or 1 values. NameAndPublisher arpNameAndPublisher( - NormalizeAndPrepareName(arpEntry.Entry->GetInstalledVersion()->GetProperty(PackageVersionProperty::Name).get()), - NormalizeAndPreparePublisher(arpEntry.Entry->GetInstalledVersion()->GetProperty(PackageVersionProperty::Publisher).get())); + NormalizeAndPrepareName(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Name).get()), + NormalizeAndPreparePublisher(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Publisher).get())); // Get the best score across all localizations double bestMatchingScore = 0; diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -406,10 +406,12 @@ <ClInclude Include="Public\winget\CheckpointDatabase.h" /> <ClInclude Include="Public\winget\IconExtraction.h" /> <ClInclude Include="Public\winget\InstalledFilesCorrelation.h" /> + <ClInclude Include="Public\winget\InstalledStatus.h" /> <ClInclude Include="Public\winget\InstallerMetadataCollectionContext.h" /> <ClInclude Include="Public\winget\ManifestJSONParser.h" /> <ClInclude Include="Public\winget\ARPCorrelationAlgorithms.h" /> <ClInclude Include="Public\winget\PackageTrackingCatalog.h" /> + <ClInclude Include="Public\winget\PackageVersionSelection.h" /> <ClInclude Include="Public\winget\PinningData.h" /> <ClInclude Include="Public\winget\PortableIndex.h" /> <ClInclude Include="Public\winget\RepositorySearch.h" /> @@ -493,6 +495,7 @@ <ClCompile Include="PackageDependenciesValidation.cpp" /> <ClCompile Include="PackageInstalledStatus.cpp" /> <ClCompile Include="PackageTrackingCatalog.cpp" /> + <ClCompile Include="PackageVersionSelection.cpp" /> <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -321,9 +321,6 @@ <ClInclude Include="Microsoft\Schema\Portable_1_0\PortableTable.h"> <Filter>Microsoft\Schema\Portable_1_0</Filter> </ClInclude> - <ClInclude Include="Microsoft\PortableIndex.h"> - <Filter>Microsoft</Filter> - </ClInclude> <ClInclude Include="Microsoft\Schema\IPortableIndex.h"> <Filter>Microsoft\Schema</Filter> </ClInclude> @@ -381,9 +378,6 @@ <ClInclude Include="Microsoft\Schema\ICheckpointDatabase.h"> <Filter>Microsoft\Schema</Filter> </ClInclude> - <ClInclude Include="Microsoft\CheckpointDatabase.h"> - <Filter>Microsoft</Filter> - </ClInclude> <ClInclude Include="Microsoft\Schema\Checkpoint_1_0\CheckpointDataTable.h"> <Filter>Microsoft\Schema\Checkpoint_1_0</Filter> </ClInclude> @@ -405,6 +399,21 @@ <ClInclude Include="Rest\Schema\AuthenticationInfoParser.h"> <Filter>Rest\Schema</Filter> </ClInclude> + <ClInclude Include="Public\winget\PinningData.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="Public\winget\PortableIndex.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="Public\winget\CheckpointDatabase.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="Public\winget\InstalledStatus.h"> + <Filter>Public\winget</Filter> + </ClInclude> + <ClInclude Include="Public\winget\PackageVersionSelection.h"> + <Filter>Public\winget</Filter> + </ClInclude> <ClInclude Include="Rest\Schema\1_7\Json\ManifestDeserializer.h"> <Filter>Rest\Schema\1_7\Json</Filter> </ClInclude> @@ -638,6 +647,12 @@ <ClCompile Include="Rest\Schema\AuthenticationInfoParser.cpp"> <Filter>Rest\Schema</Filter> </ClCompile> + <ClCompile Include="PinningData.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="PackageVersionSelection.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="Rest\Schema\1_7\Json\ManifestDeserializer_1_7.cpp"> <Filter>Rest\Schema\1_7\Json</Filter> </ClCompile> diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -34,10 +34,22 @@ namespace AppInstaller::Repository return false; } + // Gets the only available package from the composite, ensuring this fact in test contexts. + std::shared_ptr<IPackage> OnlyAvailable(const std::shared_ptr<ICompositePackage>& composite) + { + std::vector<std::shared_ptr<IPackage>> availablePackages = composite->GetAvailable(); + +#ifndef AICLI_DISABLE_TEST_HOOKS + THROW_HR_IF(E_UNEXPECTED, availablePackages.size() != 1); +#endif + + return std::move(availablePackages.front()); + } + // Move returns if there is only one package in the matches that is strong; otherwise returns an empty value. - std::shared_ptr<IPackage> FindOnlyStrongMatchFieldResult(std::vector<ResultMatch>& matches) + std::shared_ptr<ICompositePackage> FindOnlyStrongMatchFieldResult(std::vector<ResultMatch>& matches) { - std::shared_ptr<IPackage> result; + std::shared_ptr<ICompositePackage> result; for (auto&& match : matches) { @@ -63,7 +75,7 @@ namespace AppInstaller::Repository // Gets a single matching package from the results template <typename MultipleIntro, typename Indeterminate> - std::shared_ptr<IPackage> GetMatchingPackage(std::vector<ResultMatch>& matches, MultipleIntro&& multipleIntro, Indeterminate&& indeterminate) + std::shared_ptr<ICompositePackage> GetMatchingPackage(std::vector<ResultMatch>& matches, MultipleIntro&& multipleIntro, Indeterminate&& indeterminate) { if (matches.empty()) { @@ -91,14 +103,15 @@ namespace AppInstaller::Repository // For a given package from a tracking catalog, get the latest write time and its corresponding package version. // Look at all versions rather than just the latest to account for the potential of downgrading. std::pair<std::chrono::system_clock::time_point, std::shared_ptr<IPackageVersion>> GetLatestTrackingWriteTimeAndPackageVersion( - const std::shared_ptr<IPackage>& trackingPackage) + const std::shared_ptr<ICompositePackage>& trackingCompositePackage) { + std::shared_ptr<IPackage> trackingPackage = OnlyAvailable(trackingCompositePackage); std::chrono::system_clock::time_point resultTime{}; std::shared_ptr<IPackageVersion> resultVersion; - for (const auto& key : trackingPackage->GetAvailableVersionKeys()) + for (const auto& key : trackingPackage->GetVersionKeys()) { - auto version = trackingPackage->GetAvailableVersion(key); + auto version = trackingPackage->GetVersion(key); if (version) { auto metadata = version->GetMetadata(); @@ -136,12 +149,12 @@ namespace AppInstaller::Repository { // Stores raw versions value strings to run a preliminary check whether version mapping is needed. std::vector<std::tuple<std::string, std::string, std::string>> rawVersionValues; - auto versionKeys = availablePackage->GetAvailableVersionKeys(); + auto versionKeys = availablePackage->GetVersionKeys(); bool shouldTryPerformMapping = false; for (auto const& versionKey : versionKeys) { - auto availableVersion = availablePackage->GetAvailableVersion(versionKey); + auto availableVersion = availablePackage->GetVersion(versionKey); std::string arpMinVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMinVersion); std::string arpMaxVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMaxVersion); @@ -327,163 +340,128 @@ namespace AppInstaller::Repository std::shared_ptr<IPackageVersion> m_trackingPackageVersion; }; - // A composite package for the CompositeSource. - struct CompositePackage : public IPackage + // An IPackage for the installed package of a CompositePackage. + // Supports only a single version of a single package at this time. + struct CompositeInstalledPackage : public IPackage { - static constexpr IPackageType PackageType = IPackageType::CompositePackage; + static constexpr IPackageType PackageType = IPackageType::CompositeInstalledPackage; - CompositePackage(std::shared_ptr<IPackage> installedPackage, std::shared_ptr<IPackage> availablePackage = {}) - { - // Grab the installed version's channel to allow for filtering in calls to get available info. - if (installedPackage) - { - m_installedPackage = std::move(installedPackage); - auto installedVersion = m_installedPackage->GetInstalledVersion(); - if (installedVersion) - { - m_installedChannel = installedVersion->GetProperty(PackageVersionProperty::Channel); - } - } + CompositeInstalledPackage(std::shared_ptr<IPackage> package, Source trackingSource, std::shared_ptr<IPackageVersion> trackingPackageVersion, std::string overrideVersion = {}) : + m_package(std::move(package)), m_trackingSource(std::move(trackingSource)), m_trackingPackageVersion(std::move(trackingPackageVersion)), m_overrideVersion(std::move(overrideVersion)) + {} - AddAvailablePackage(std::move(availablePackage)); + Utility::LocIndString GetProperty(PackageProperty property) const override + { + return m_package->GetProperty(property); } - Utility::LocIndString GetProperty(PackageProperty property) const override + std::vector<PackageVersionKey> GetVersionKeys() const override { - std::shared_ptr<IPackageVersion> truth; - if (m_defaultAvailablePackage) - { - truth = m_defaultAvailablePackage->GetLatestAvailableVersion(); - } - if (!truth) - { - truth = m_trackingPackageVersion; - } - if (!truth) - { - truth = GetInstalledVersion(); - } - if (!truth) + auto result = m_package->GetVersionKeys(); + THROW_HR_IF(E_UNEXPECTED, result.size() != 1); + if (!m_overrideVersion.empty()) { - truth = GetLatestAvailableVersion(); + result.front().Version = m_overrideVersion; } + return result; + } - switch (property) - { - case PackageProperty::Id: - return truth->GetProperty(PackageVersionProperty::Id); - case PackageProperty::Name: - return truth->GetProperty(PackageVersionProperty::Name); - default: - THROW_HR(E_UNEXPECTED); - } + std::shared_ptr<IPackageVersion> GetVersion(const PackageVersionKey& versionKey) const + { + return (GetVersionKeys().front().IsMatch(versionKey)) ? GetLatestVersion() : std::shared_ptr<IPackageVersion>{}; } - std::shared_ptr<IPackageVersion> GetInstalledVersion() const override + std::shared_ptr<IPackageVersion> GetLatestVersion() const override { - if (m_installedPackage) + return std::make_shared<CompositeInstalledVersion>(m_package->GetLatestVersion(), m_trackingSource, m_trackingPackageVersion, m_overrideVersion); + } + + Source GetSource() const override + { + // If there is a tracking source, use it instead to indicate that it came from there. + if (m_trackingSource) { - auto installedVersion = m_installedPackage->GetInstalledVersion(); - if (installedVersion) - { - return std::make_shared<CompositeInstalledVersion>(std::move(installedVersion), m_trackingSource, m_trackingPackageVersion, m_overrideInstalledVersion); - } + return m_trackingSource; } - return {}; + return m_package->GetSource(); } - std::vector<PackageVersionKey> GetAvailableVersionKeys() const override + bool IsSame(const IPackage* other) const override { - std::vector<PackageVersionKey> result; + const CompositeInstalledPackage* otherPackage = PackageCast<const CompositeInstalledPackage*>(other); - for (const auto& availablePackage : m_availablePackages) + if (otherPackage) { - auto versionKeys = availablePackage->GetAvailableVersionKeys(); - std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); + return m_package->IsSame(otherPackage->m_package.get()); } - // Remove all elements whose channel does not match the installed package. - std::string_view channel = m_installedChannel; - result.erase( - std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, channel); }), - result.end()); - - // Put latest versions at the front; for versions available from multiple sources maintain the order they were added in - std::stable_sort(result.begin(), result.end()); - - return result; + return false; } - std::shared_ptr<IPackageVersion> GetLatestAvailableVersion() const override + const void* CastTo(IPackageType type) const override { - return GetAvailableVersion({ "", "", m_installedChannel.get() }); + if (type == PackageType) + { + return this; + } + + return nullptr; } - std::shared_ptr<IPackageVersion> GetAvailableVersion(const PackageVersionKey& versionKey) const override + private: + std::shared_ptr<IPackage> m_package; + Source m_trackingSource; + std::shared_ptr<IPackageVersion> m_trackingPackageVersion; + std::string m_overrideVersion; + }; + + // An ICompositePackage for the CompositeSource. + struct CompositePackage : public ICompositePackage + { + // The availablePackage may only contain one available package within it, as it is expected to be the output of a search on a single source. + CompositePackage(const std::shared_ptr<ICompositePackage>& installedPackage, const std::shared_ptr<ICompositePackage>& availablePackage = {}) { - for (const auto& availablePackage : m_availablePackages) + if (installedPackage) { - if (!Utility::IsEmptyOrWhitespace(versionKey.SourceId)) - { - auto latestAvailable = availablePackage->GetLatestAvailableVersion(); - if (latestAvailable && versionKey.SourceId != latestAvailable->GetSource().GetIdentifier()) - { - continue; - } - } - - return availablePackage->GetAvailableVersion(versionKey); + m_installedPackage = installedPackage->GetInstalled(); } - return {}; + AddAvailablePackage(availablePackage); } - bool IsSame(const IPackage* other) const override + Utility::LocIndString GetProperty(PackageProperty property) const override { - const CompositePackage* otherComposite = PackageCast<const CompositePackage*>(other); - - if (!otherComposite || - static_cast<bool>(m_installedPackage) != static_cast<bool>(otherComposite->m_installedPackage) || - m_availablePackages.size() != otherComposite->m_availablePackages.size()) + std::shared_ptr<IPackage> truth; + if (m_defaultAvailablePackage) { - return false; + truth = m_defaultAvailablePackage; } - - if (m_installedPackage && !m_installedPackage->IsSame(otherComposite->m_installedPackage.get())) + if (!truth) { - return false; + truth = m_trackingPackage; } - - for (const auto& availablePackage : m_availablePackages) + if (!truth) { - bool foundMatch = false; - for (const auto& otherAvailablePackage : otherComposite->m_availablePackages) - { - if (availablePackage->IsSame(otherAvailablePackage.get())) - { - foundMatch = true; - break; - } - } - - if (!foundMatch) - { - return false; - } + truth = m_installedPackage; } - return true; + return truth->GetProperty(property); } - const void* CastTo(IPackageType type) const override + std::shared_ptr<IPackage> GetInstalled() override { - if (type == PackageType) + if (m_installedPackage) { - return this; + return std::make_shared<CompositeInstalledPackage>(m_installedPackage, m_trackingSource, m_trackingPackageVersion, m_overrideInstalledVersion); } - return nullptr; + return {}; + } + + std::vector<std::shared_ptr<IPackage>> GetAvailable() override + { + return m_availablePackages; } bool IsSameAsAnyAvailable(const IPackage* other) const @@ -504,14 +482,7 @@ namespace AppInstaller::Repository std::shared_ptr<IPackage> GetInstalledPackage() const { - if (m_installedPackage) - { - return m_installedPackage; - } - else - { - return {}; - } + return m_installedPackage; } const std::shared_ptr<IPackage>& GetTrackingPackage() const @@ -519,18 +490,20 @@ namespace AppInstaller::Repository return m_trackingPackage; } - void AddAvailablePackage(std::shared_ptr<IPackage> availablePackage) + void AddAvailablePackage(const std::shared_ptr<ICompositePackage>& availablePackage) { if (availablePackage) { + std::shared_ptr<IPackage> singlePackage = OnlyAvailable(availablePackage); + if (!m_defaultAvailablePackage) { // Set override only with the first available version found - m_defaultAvailablePackage = availablePackage; + m_defaultAvailablePackage = singlePackage; TrySetOverrideInstalledVersion(m_defaultAvailablePackage); } - m_availablePackages.emplace_back(std::move(availablePackage)); + m_availablePackages.emplace_back(std::move(singlePackage)); } } @@ -541,13 +514,18 @@ namespace AppInstaller::Repository m_trackingPackageVersion = std::move(trackingPackageVersion); } + const Source& GetTrackingSource() const + { + return m_trackingSource; + } + private: // Try to set a version that will override the version string from the installed package void TrySetOverrideInstalledVersion(std::shared_ptr<IPackage> availablePackage) { if (m_installedPackage && availablePackage) { - auto installedVersion = m_installedPackage->GetInstalledVersion(); + auto installedVersion = m_installedPackage->GetLatestVersion(); if (installedVersion) { auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); @@ -560,7 +538,6 @@ namespace AppInstaller::Repository } std::shared_ptr<IPackage> m_installedPackage; - Utility::LocIndString m_installedChannel; Source m_trackingSource; std::shared_ptr<IPackage> m_trackingPackage; std::shared_ptr<IPackageVersion> m_trackingPackageVersion; @@ -697,10 +674,11 @@ namespace AppInstaller::Repository // downloadManifests: when creating system reference strings, also download manifests to get more data. std::optional<PackageData> CheckForExistingResultFromAvailablePackageMatch(const ResultMatch& availableMatch, bool downloadManifests) { + std::shared_ptr<IPackage> availablePackage = OnlyAvailable(availableMatch.Package); + for (auto& match : Matches) { - const CompositePackage* compositeMatch = PackageCast<const CompositePackage*>(match.Package.get()); - if (compositeMatch && compositeMatch->IsSameAsAnyAvailable(availableMatch.Package.get())) + if (match.Package->IsSameAsAnyAvailable(availablePackage.get())) { if (ResultMatchComparator{}(availableMatch, match)) { @@ -714,9 +692,9 @@ namespace AppInstaller::Repository PackageData result; constexpr int c_downloadManifestsLimit = 3; int manifestsDownloaded = 0; - for (auto const& versionKey : availableMatch.Package->GetAvailableVersionKeys()) + for (auto const& versionKey : availablePackage->GetVersionKeys()) { - auto packageVersion = availableMatch.Package->GetAvailableVersion(versionKey); + auto packageVersion = availablePackage->GetVersion(versionKey); AddSystemReferenceStrings(packageVersion.get(), result); if (downloadManifests && manifestsDownloaded < c_downloadManifestsLimit) @@ -734,10 +712,12 @@ namespace AppInstaller::Repository // If we don't, return package data for further use. std::optional<PackageData> CheckForExistingResultFromTrackingPackageMatch(const ResultMatch& trackingMatch) { + std::shared_ptr<IPackage> trackingMatchPackage = OnlyAvailable(trackingMatch.Package); + for (auto& match : Matches) { const std::shared_ptr<IPackage>& trackingPackage = match.Package->GetTrackingPackage(); - if (trackingPackage && trackingPackage->IsSame(trackingMatch.Package.get())) + if (trackingPackage && trackingPackage->IsSame(trackingMatchPackage.get())) { if (ResultMatchComparator{}(trackingMatch, match)) { @@ -749,9 +729,9 @@ namespace AppInstaller::Repository } PackageData result; - for (auto const& versionKey : trackingMatch.Package->GetAvailableVersionKeys()) + for (auto const& versionKey : trackingMatchPackage->GetVersionKeys()) { - auto packageVersion = trackingMatch.Package->GetAvailableVersion(versionKey); + auto packageVersion = trackingMatchPackage->GetVersion(versionKey); AddSystemReferenceStrings(packageVersion.get(), result); } return result; @@ -923,7 +903,7 @@ namespace AppInstaller::Repository } }; - std::shared_ptr<IPackage> GetTrackedPackageFromAvailableSource(CompositeResult& result, const Source& source, const Utility::LocIndString& identifier) + std::shared_ptr<ICompositePackage> GetTrackedPackageFromAvailableSource(CompositeResult& result, const Source& source, const Utility::LocIndString& identifier) { SearchRequest directRequest; directRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, identifier.get()); @@ -936,7 +916,7 @@ namespace AppInstaller::Repository } else if (directResult.Matches.size() == 1) { - return std::move(directResult.Matches[0].Package); + return directResult.Matches[0].Package; } else { @@ -1049,9 +1029,14 @@ namespace AppInstaller::Repository continue; } - auto compositePackage = std::make_shared<CompositePackage>(match.Package); + std::shared_ptr<CompositePackage> compositePackage = std::make_shared<CompositePackage>(match.Package); + std::shared_ptr<IPackageVersion> installedVersion; - auto installedVersion = compositePackage->GetInstalledVersion(); + auto installedPackage = compositePackage->GetInstalled(); + if (installedPackage) + { + installedVersion = installedPackage->GetLatestVersion(); + } if (!installedVersion) { @@ -1075,6 +1060,7 @@ namespace AppInstaller::Repository std::shared_ptr<IPackage> trackingPackage; std::shared_ptr<IPackageVersion> trackingPackageVersion; std::chrono::system_clock::time_point trackingPackageTime; + bool foundAvailablePackageFromTracking = false; // Check the tracking catalog first to see if there is a correlation there. // TODO: When the issue with support for multiple available packages is fixed, this should move into @@ -1084,7 +1070,7 @@ namespace AppInstaller::Repository auto trackingCatalog = source.GetTrackingCatalog(); SearchResult trackingResult = trackingCatalog.Search(systemReferenceSearch); - std::shared_ptr<IPackage> candidatePackage = GetMatchingPackage(trackingResult.Matches, + std::shared_ptr<ICompositePackage> candidatePackage = GetMatchingPackage(trackingResult.Matches, [&]() { AICLI_LOG(Repo, Info, << "Found multiple matches for installed package [" << installedVersion->GetProperty(PackageVersionProperty::Id) << @@ -1101,7 +1087,7 @@ namespace AppInstaller::Repository if (!trackingPackage || candidateTime > trackingPackageTime) { trackedSource = source; - trackingPackage = std::move(candidatePackage); + trackingPackage = OnlyAvailable(candidatePackage); trackingPackageVersion = std::move(candidateVersion); trackingPackageTime = candidateTime; } @@ -1115,6 +1101,7 @@ namespace AppInstaller::Repository if (availablePackage) { compositePackage->AddAvailablePackage(std::move(availablePackage)); + foundAvailablePackageFromTracking = true; } compositePackage->SetTracking(std::move(trackedSource), std::move(trackingPackage), std::move(trackingPackageVersion)); } @@ -1128,6 +1115,12 @@ namespace AppInstaller::Repository continue; } + // Skip the source that the tracking correlation result came from if we found one + if (foundAvailablePackageFromTracking && compositePackage->GetTrackingSource() == source) + { + continue; + } + SearchResult availableResult = result.SearchAndHandleFailures(source, systemReferenceSearch); if (availableResult.Matches.empty()) @@ -1146,7 +1139,7 @@ namespace AppInstaller::Repository }); // For non pinning cases. We found some matching packages here, don't keep going. - compositePackage->AddAvailablePackage(std::move(availablePackage)); + compositePackage->AddAvailablePackage(availablePackage); } } @@ -1189,7 +1182,7 @@ namespace AppInstaller::Repository // Correlate against installed (allow exceptions out as we own the installed source) SearchResult installedCrossRef = m_installedSource.Search(systemReferenceSearch); - std::shared_ptr<IPackage> installedPackage = GetMatchingPackage(installedCrossRef.Matches, + auto installedPackage = GetMatchingPackage(installedCrossRef.Matches, [&]() { AICLI_LOG(Repo, Info, << "Found multiple matches for tracking package [" << match.Package->GetProperty(PackageProperty::Id) << @@ -1198,15 +1191,15 @@ namespace AppInstaller::Repository AICLI_LOG(Repo, Warning, << " Appropriate installed package could not be determined"); }); - if (installedPackage && !result.ContainsInstalledPackage(installedPackage.get())) + if (installedPackage && !result.ContainsInstalledPackage(installedPackage->GetInstalled().get())) { auto compositePackage = std::make_shared<CompositePackage>( - std::move(installedPackage), + installedPackage, GetTrackedPackageFromAvailableSource(result, source, match.Package->GetProperty(PackageProperty::Id))); auto [writeTime, trackingPackageVersion] = GetLatestTrackingWriteTimeAndPackageVersion(match.Package); - compositePackage->SetTracking(source, std::move(match.Package), std::move(trackingPackageVersion)); + compositePackage->SetTracking(source, OnlyAvailable(match.Package), std::move(trackingPackageVersion)); result.Matches.emplace_back(std::move(compositePackage), match.MatchCriteria); } @@ -1241,7 +1234,7 @@ namespace AppInstaller::Repository // Correlate against installed (allow exceptions out as we own the installed source) SearchResult installedCrossRef = m_installedSource.Search(systemReferenceSearch); - std::shared_ptr<IPackage> installedPackage = GetMatchingPackage(installedCrossRef.Matches, + auto installedPackage = GetMatchingPackage(installedCrossRef.Matches, [&]() { AICLI_LOG(Repo, Info, << "Found multiple matches for available package [" << match.Package->GetProperty(PackageProperty::Id) << @@ -1250,18 +1243,18 @@ namespace AppInstaller::Repository AICLI_LOG(Repo, Warning, << " Appropriate installed package could not be determined"); }); - if (installedPackage && !result.ContainsInstalledPackage(installedPackage.get())) + if (installedPackage && !result.ContainsInstalledPackage(installedPackage->GetInstalled().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); + result.Matches.emplace_back(std::make_shared<CompositePackage>(installedPackage, std::move(match.Package)), match.MatchCriteria); } } // If there was no correlation for this package, add it without one. if ((m_searchBehavior == CompositeSearchBehavior::AllPackages || m_searchBehavior == CompositeSearchBehavior::AvailablePackages) && !foundInstalledMatch) { - result.Matches.emplace_back(std::make_shared<CompositePackage>(std::shared_ptr<IPackage>{}, std::move(match.Package)), match.MatchCriteria); + result.Matches.emplace_back(std::make_shared<CompositePackage>(std::shared_ptr<ICompositePackage>{}, std::move(match.Package)), match.MatchCriteria); } } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -214,17 +214,20 @@ namespace AppInstaller::Repository::Microsoft SQLiteIndex::IdType m_manifestId; }; - // The base for IPackage implementations here. - struct PackageBase : public SourceReference + // The IPackage implementation here. + struct SQLitePackage : public std::enable_shared_from_this<SQLitePackage>, public SourceReference, public IPackage, public ICompositePackage { - PackageBase(const std::shared_ptr<SQLiteIndexSource>& source, SQLiteIndex::IdType idId) : - SourceReference(source), m_idId(idId) {} + static constexpr IPackageType PackageType = IPackageType::SQLitePackage; + SQLitePackage(const std::shared_ptr<SQLiteIndexSource>& source, SQLiteIndex::IdType idId, bool isInstalled) : + SourceReference(source), m_idId(idId), m_isInstalled(isInstalled) {} + + // Inherited via IPackage Utility::LocIndString GetProperty(PackageProperty property) const { Utility::LocIndString result; - std::shared_ptr<IPackageVersion> truth = GetLatestVersionInternal(); + std::shared_ptr<IPackageVersion> truth = GetLatestVersion(); if (truth) { switch (property) @@ -239,70 +242,30 @@ namespace AppInstaller::Repository::Microsoft } else { - AICLI_LOG(Repo, Verbose, << "PackageBase: No manifest was found for the package with id# '" << m_idId << "'"); + AICLI_LOG(Repo, Verbose, << "SQLitePackage: No manifest was found for the package with id# '" << m_idId << "'"); } return result; } - bool IsSame(const PackageBase& other) const - { - return GetReferenceSource()->IsSame(other.GetReferenceSource().get()) && m_idId == other.m_idId; - } - - protected: - std::shared_ptr<IPackageVersion> GetLatestVersionInternal() const + std::vector<PackageVersionKey> GetVersionKeys() const override { std::shared_ptr<SQLiteIndexSource> source = GetReferenceSource(); - std::optional<SQLiteIndex::IdType> manifestId = source->GetIndex().GetManifestIdByKey(m_idId, {}, {}); - if (manifestId) { - return std::make_shared<PackageVersion>(source, manifestId.value()); - } - - return {}; - } + auto sharedLock = m_versionKeysLock.lock_shared(); - SQLiteIndex::IdType m_idId; - }; - - // The IPackage impl for SQLiteIndexSource of Available packages. - struct AvailablePackage : public PackageBase, public IPackage - { - using PackageBase::PackageBase; - - static constexpr IPackageType PackageType = IPackageType::SQLiteAvailablePackage; - - // Inherited via IPackage - Utility::LocIndString GetProperty(PackageProperty property) const override - { - return PackageBase::GetProperty(property); - } - - std::shared_ptr<IPackageVersion> GetInstalledVersion() const override - { - return {}; - } - - std::vector<PackageVersionKey> GetAvailableVersionKeys() const override - { - std::shared_ptr<SQLiteIndexSource> source = GetReferenceSource(); - - { - auto sharedLock = m_availableVersionKeysLock.lock_shared(); - - if (!m_availableVersionKeys.empty()) + if (!m_versionKeys.empty()) { - return m_availableVersionKeys; + return m_versionKeys; } } - auto exclusiveLock = m_availableVersionKeysLock.lock_exclusive(); + auto exclusiveLock = m_versionKeysLock.lock_exclusive(); - if (!m_availableVersionKeys.empty()) + if (!m_versionKeys.empty()) { - return m_availableVersionKeys; + return m_versionKeys; } std::vector<SQLiteIndex::VersionKey> versions = source->GetIndex().GetVersionKeysById(m_idId); @@ -311,19 +274,27 @@ namespace AppInstaller::Repository::Microsoft { std::string version = vk.VersionAndChannel.GetVersion().ToString(); std::string channel = vk.VersionAndChannel.GetChannel().ToString(); - m_availableVersionKeys.emplace_back(source->GetIdentifier(), version, channel); - m_availableVersionKeysMap.emplace(MapKey{ std::move(version), std::move(channel) }, vk.ManifestId); + m_versionKeys.emplace_back(source->GetIdentifier(), version, channel); + m_versionKeysMap.emplace(MapKey{ std::move(version), std::move(channel) }, vk.ManifestId); } - return m_availableVersionKeys; + return m_versionKeys; } - std::shared_ptr<IPackageVersion> GetLatestAvailableVersion() const override + std::shared_ptr<IPackageVersion> GetLatestVersion() const override { - return GetLatestVersionInternal(); + std::shared_ptr<SQLiteIndexSource> source = GetReferenceSource(); + std::optional<SQLiteIndex::IdType> manifestId = source->GetIndex().GetManifestIdByKey(m_idId, {}, {}); + + if (manifestId) + { + return std::make_shared<PackageVersion>(source, manifestId.value()); + } + + return {}; } - std::shared_ptr<IPackageVersion> GetAvailableVersion(const PackageVersionKey& versionKey) const override + std::shared_ptr<IPackageVersion> GetVersion(const PackageVersionKey& versionKey) const override { std::shared_ptr<SQLiteIndexSource> source = GetReferenceSource(); @@ -337,10 +308,10 @@ namespace AppInstaller::Repository::Microsoft { MapKey requested{ versionKey.Version, versionKey.Channel }; - auto sharedLock = m_availableVersionKeysLock.lock_shared(); + auto sharedLock = m_versionKeysLock.lock_shared(); - auto itr = m_availableVersionKeysMap.find(requested); - if (itr != m_availableVersionKeysMap.end()) + auto itr = m_versionKeysMap.find(requested); + if (itr != m_versionKeysMap.end()) { manifestId = itr->second; } @@ -359,13 +330,18 @@ namespace AppInstaller::Repository::Microsoft return {}; } + Source GetSource() const override + { + return Source{ GetReferenceSource() }; + } + bool IsSame(const IPackage* other) const override { - const AvailablePackage* otherAvailable = PackageCast<const AvailablePackage*>(other); + const SQLitePackage* otherSQLite = PackageCast<const SQLitePackage*>(other); - if (otherAvailable) + if (otherSQLite) { - return PackageBase::IsSame(*otherAvailable); + return GetReferenceSource()->IsSame(otherSQLite->GetReferenceSource().get()) && m_idId == otherSQLite->m_idId; } return false; @@ -381,6 +357,17 @@ namespace AppInstaller::Repository::Microsoft return nullptr; } + // Inherited via ICompositePackage + std::shared_ptr<IPackage> GetInstalled() override + { + return m_isInstalled ? shared_from_this() : std::shared_ptr<IPackage>{}; + } + + std::vector<std::shared_ptr<IPackage>> GetAvailable() override + { + return m_isInstalled ? std::vector<std::shared_ptr<IPackage>>{} : std::vector<std::shared_ptr<IPackage>>{ shared_from_this() }; + } + private: // Contains the information needed to map a version key to it's rows. struct MapKey @@ -405,66 +392,13 @@ namespace AppInstaller::Repository::Microsoft } }; - // To avoid removing const from the interface - mutable wil::srwlock m_availableVersionKeysLock; - mutable std::vector<PackageVersionKey> m_availableVersionKeys; - mutable std::map<MapKey, SQLiteIndex::IdType> m_availableVersionKeysMap; - }; - - // The IPackage impl for SQLiteIndexSource of Installed packages. - struct InstalledPackage : public PackageBase, public IPackage - { - using PackageBase::PackageBase; - - static constexpr IPackageType PackageType = IPackageType::SQLiteInstalledPackage; - - // Inherited via IPackage - Utility::LocIndString GetProperty(PackageProperty property) const override - { - return PackageBase::GetProperty(property); - } - - std::shared_ptr<IPackageVersion> GetInstalledVersion() const override - { - return GetLatestVersionInternal(); - } - - std::vector<PackageVersionKey> GetAvailableVersionKeys() const override - { - return {}; - } - - std::shared_ptr<IPackageVersion> GetLatestAvailableVersion() const override - { - return {}; - } - - std::shared_ptr<IPackageVersion> GetAvailableVersion(const PackageVersionKey&) const override - { - return {}; - } - - bool IsSame(const IPackage* other) const override - { - const InstalledPackage* otherInstalled = PackageCast<const InstalledPackage*>(other); - - if (otherInstalled) - { - return PackageBase::IsSame(*otherInstalled); - } - - return false; - } - - const void* CastTo(IPackageType type) const override - { - if (type == PackageType) - { - return this; - } + SQLiteIndex::IdType m_idId; + bool m_isInstalled; - return nullptr; - } + // To avoid removing const from the interface + mutable wil::srwlock m_versionKeysLock; + mutable std::vector<PackageVersionKey> m_versionKeys; + mutable std::map<MapKey, SQLiteIndex::IdType> m_versionKeysMap; }; } @@ -495,18 +429,9 @@ namespace AppInstaller::Repository::Microsoft std::shared_ptr<SQLiteIndexSource> sharedThis = NonConstSharedFromThis(); for (auto& indexResult : indexResults.Matches) { - std::unique_ptr<IPackage> package; - - if (m_isInstalled) - { - package = std::make_unique<InstalledPackage>(sharedThis, indexResult.first); - } - else - { - package = std::make_unique<AvailablePackage>(sharedThis, indexResult.first); - } - - result.Matches.emplace_back(std::move(package), std::move(indexResult.second)); + result.Matches.emplace_back( + std::make_shared<SQLitePackage>(sharedThis, indexResult.first, m_isInstalled), + std::move(indexResult.second)); } result.Truncated = indexResults.Truncated; return result; diff --git a/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp b/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp @@ -1,7 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "pch.h" -#include "Public/winget/RepositorySearch.h" +#include "Public/winget/InstalledStatus.h" +#include "Public/winget/PackageVersionSelection.h" #include <winget/Filesystem.h> using namespace AppInstaller::Settings; @@ -77,14 +78,14 @@ namespace AppInstaller::Repository } std::vector<InstallerInstalledStatus> CheckInstalledStatusInternal( - const std::shared_ptr<IPackage>& package, + const std::shared_ptr<ICompositePackage>& package, InstalledStatusType checkTypes) { using namespace AppInstaller::Manifest; std::vector<InstallerInstalledStatus> result; bool checkFileHash = false; - std::shared_ptr<IPackageVersion> installedVersion = package->GetInstalledVersion(); + std::shared_ptr<IPackageVersion> installedVersion = GetInstalledVersion(package); std::shared_ptr<IPackageVersion> availableVersion; FileHashMap fileHashes; @@ -96,6 +97,8 @@ namespace AppInstaller::Repository Utility::Architecture installedArchitecture = Utility::Architecture::Unknown; HRESULT installedLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE; + std::shared_ptr<IPackageVersionCollection> availableVersions = GetAvailableVersionsForInstalledVersion(package); + // Prepare installed metadata from installed version. // Determine the available package version to be used for installed status checking. // Only perform file hash check if we find an available version that matches installed version. @@ -120,14 +123,14 @@ namespace AppInstaller::Repository { // Use the base version as available version if installed version is mapped to be an approximate. versionKey.Version = installedVersionAsVersion.GetBaseVersion().ToString(); - availableVersion = package->GetAvailableVersion(versionKey); + availableVersion = availableVersions->GetVersion(versionKey); // It's unexpected if the installed version is already mapped to some version. THROW_HR_IF(E_UNEXPECTED, !availableVersion); } else { versionKey.Version = installedVersionAsVersion.ToString(); - availableVersion = package->GetAvailableVersion(versionKey); + availableVersion = availableVersions->GetVersion(versionKey); if (availableVersion) { checkFileHash = true; @@ -139,7 +142,7 @@ namespace AppInstaller::Repository { // No installed version, or installed version not found in available versions, // then attempt to check installed status using latest version. - availableVersion = package->GetLatestAvailableVersion(); + availableVersion = availableVersions->GetLatestVersion(); THROW_HR_IF(E_UNEXPECTED, !availableVersion); } @@ -237,7 +240,7 @@ namespace AppInstaller::Repository } } - std::vector<InstallerInstalledStatus> CheckPackageInstalledStatus(const std::shared_ptr<IPackage>& package, InstalledStatusType checkTypes) + std::vector<InstallerInstalledStatus> CheckPackageInstalledStatus(const std::shared_ptr<ICompositePackage>& package, InstalledStatusType checkTypes) { return CheckInstalledStatusInternal(package, checkTypes); } diff --git a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/PackageVersionSelection.h" +#include "Public/winget/RepositorySource.h" + + +namespace AppInstaller::Repository +{ + namespace + { + std::shared_ptr<IPackage> GetAvailablePackageFromSource(const std::vector<std::shared_ptr<IPackage>>& packages, const std::string_view sourceIdentifier) + { + for (const std::shared_ptr<IPackage>& package : packages) + { + if (sourceIdentifier == package->GetSource().GetIdentifier()) + { + return package; + } + } + + return {}; + } + + struct AvailablePackageVersionCollection : public IPackageVersionCollection + { + AvailablePackageVersionCollection(const std::shared_ptr<ICompositePackage>& composite, const std::shared_ptr<IPackageVersion>& installedVersion) : + m_packages(composite->GetAvailable()) + { + if (!installedVersion) + { + return; + } + + m_channel = installedVersion->GetProperty(PackageVersionProperty::Channel); + + // Remove the packages that are not from the installed source. + Source installedVersionSource = installedVersion->GetSource(); + if (installedVersionSource && installedVersionSource.ContainsAvailablePackages()) + { + m_packages.erase(std::remove_if(m_packages.begin(), m_packages.end(), [&](const std::shared_ptr<IPackage>& p) { return installedVersionSource != p->GetSource(); }), m_packages.end()); + } + } + + std::vector<PackageVersionKey> GetVersionKeys() const override + { + std::vector<PackageVersionKey> result; + + for (const std::shared_ptr<IPackage>& package : m_packages) + { + std::vector<PackageVersionKey> versionKeys = package->GetVersionKeys(); + std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); + } + + // Remove all elements whose channel does not match the installed package. + if (m_channel) + { + result.erase( + std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, m_channel.value()); }), + result.end()); + } + + // Put latest versions at the front; for versions available from multiple sources maintain the order they were added in + std::stable_sort(result.begin(), result.end()); + + return result; + } + + std::shared_ptr<IPackageVersion> GetVersion(const PackageVersionKey& versionKey) const override + { + // If there is a specific source, just use that package's result + std::shared_ptr<IPackage> package; + + if (!versionKey.SourceId.empty()) + { + package = GetAvailablePackageFromSource(m_packages, versionKey.SourceId); + } + else + { + // Otherwise, find the first version that matches + std::vector<PackageVersionKey> versions = GetVersionKeys(); + + for (const PackageVersionKey& key : versions) + { + if (key.IsMatch(versionKey)) + { + package = GetAvailablePackageFromSource(m_packages, key.SourceId); + break; + } + } + } + + return package ? package->GetVersion(versionKey) : nullptr; + } + + std::shared_ptr<IPackageVersion> GetLatestVersion() const override + { + return GetVersion({ "", "", m_channel.value_or("") }); + } + + private: + std::optional<std::string> m_channel; + std::vector<std::shared_ptr<IPackage>> m_packages; + }; + } + + std::shared_ptr<IPackageVersionCollection> GetAvailableVersionsForInstalledVersion(const std::shared_ptr<ICompositePackage>& composite) + { + return std::make_shared<AvailablePackageVersionCollection>(composite, GetInstalledVersion(composite)); + } + + std::shared_ptr<IPackageVersionCollection> GetAvailableVersionsForInstalledVersion( + const std::shared_ptr<ICompositePackage>& composite, + const std::shared_ptr<IPackageVersion>& installedVersion) + { + return std::make_shared<AvailablePackageVersionCollection>(composite, installedVersion); + } + + std::shared_ptr<IPackageVersionCollection> GetAllAvailableVersions(const std::shared_ptr<ICompositePackage>& composite) + { + return GetAvailableVersionsForInstalledVersion(composite, nullptr); + } + + std::shared_ptr<IPackageVersion> GetInstalledVersion(const std::shared_ptr<ICompositePackage>& composite) + { + auto installedPackage = composite->GetInstalled(); + return installedPackage ? installedPackage->GetLatestVersion() : nullptr; + } + + std::shared_ptr<IPackage> GetAvailablePackageFromSource(const std::shared_ptr<ICompositePackage>& composite, const std::string_view sourceIdentifier) + { + return GetAvailablePackageFromSource(composite->GetAvailable(), sourceIdentifier); + } +} diff --git a/src/AppInstallerRepositoryCore/PinningData.cpp b/src/AppInstallerRepositoryCore/PinningData.cpp @@ -145,19 +145,19 @@ namespace AppInstaller::Pinning PinningData::PinStateEvaluator::~PinStateEvaluator() = default; - std::shared_ptr<IPackageVersion> PinningData::PinStateEvaluator::GetLatestAvailableVersionForPins(const std::shared_ptr<IPackage>& package) + std::shared_ptr<IPackageVersion> PinningData::PinStateEvaluator::GetLatestAvailableVersionForPins(const std::shared_ptr<IPackageVersionCollection>& package) { if (!m_database) { - return package->GetLatestAvailableVersion(); + return package->GetLatestVersion(); } - auto availableVersionKeys = package->GetAvailableVersionKeys(); + auto availableVersionKeys = package->GetVersionKeys(); // Skip until we find a version that isn't pinned for (const auto& availableVersion : availableVersionKeys) { - std::shared_ptr<IPackageVersion> packageVersion = package->GetAvailableVersion(availableVersion); + std::shared_ptr<IPackageVersion> packageVersion = package->GetVersion(availableVersion); if (EvaluatePinType(packageVersion) == Pinning::PinType::Unknown) { return packageVersion; diff --git a/src/AppInstallerRepositoryCore/Public/winget/InstalledStatus.h b/src/AppInstallerRepositoryCore/Public/winget/InstalledStatus.h @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <AppInstallerStrings.h> +#include <winget/Manifest.h> +#include <winget/RepositorySearch.h> + +#include <memory> +#include <vector> + + +namespace AppInstaller::Repository +{ + // Defines the installed status check type. + enum class InstalledStatusType : uint32_t + { + // None is checked. + None = 0x0, + // Check Apps and Features entry. + AppsAndFeaturesEntry = 0x0001, + // Check Apps and Features entry install location if applicable. + AppsAndFeaturesEntryInstallLocation = 0x0002, + // Check Apps and Features entry install location with installed files if applicable. + AppsAndFeaturesEntryInstallLocationFile = 0x0004, + // Check default install location if applicable. + DefaultInstallLocation = 0x0008, + // Check default install location with installed files if applicable. + DefaultInstallLocationFile = 0x0010, + + // Below are helper values for calling CheckInstalledStatus as input. + // AppsAndFeaturesEntry related checks + AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, + // DefaultInstallLocation related checks + AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, + // All checks + AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, + }; + + DEFINE_ENUM_FLAG_OPERATORS(InstalledStatusType); + + // Struct representing an individual installed status. + struct InstalledStatus + { + // The installed status type. + InstalledStatusType Type = InstalledStatusType::None; + // The installed status path. + Utility::NormalizedString Path; + // The installed status result. + HRESULT Status; + + InstalledStatus(InstalledStatusType type, Utility::NormalizedString path, HRESULT status) : + Type(type), Path(std::move(path)), Status(status) {} + }; + + // Struct representing installed status from an installer. + struct InstallerInstalledStatus + { + Manifest::ManifestInstaller Installer; + std::vector<InstalledStatus> Status; + }; + + // Checks installed status of a package. + std::vector<InstallerInstalledStatus> CheckPackageInstalledStatus(const std::shared_ptr<ICompositePackage>& package, InstalledStatusType checkTypes = InstalledStatusType::AllChecks); +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <winget/RepositorySearch.h> + + +namespace AppInstaller::Repository +{ + // Gets an IPackageVersionCollection that represents the available package versions for the installed version. + // If we have tracking data, will remove packages not from the tracked source. Will also remove versions that do not correspond to the tracked channel. + // This function uses the latest installed version as a temporary convenience until side-by-side is implemented. + std::shared_ptr<IPackageVersionCollection> GetAvailableVersionsForInstalledVersion(const std::shared_ptr<ICompositePackage>& composite); + + // Gets an IPackageVersionCollection that represents the available package versions for the given installed version. + std::shared_ptr<IPackageVersionCollection> GetAvailableVersionsForInstalledVersion( + const std::shared_ptr<ICompositePackage>& composite, + const std::shared_ptr<IPackageVersion>& installedVersion); + + // Equivalent to `GetAvailableVersionsForInstalledVersion(composite, nullptr)` to make the intent more clear that the caller wants to ignore any installed + // package information. + std::shared_ptr<IPackageVersionCollection> GetAllAvailableVersions(const std::shared_ptr<ICompositePackage>& composite); + + // Gets the installed version, or a null if there isn't one. + std::shared_ptr<IPackageVersion> GetInstalledVersion(const std::shared_ptr<ICompositePackage>& composite); + + // Gets the available IPackage corresponding to the given source identifier. + std::shared_ptr<IPackage> GetAvailablePackageFromSource(const std::shared_ptr<ICompositePackage>& composite, const std::string_view sourceIdentifier); +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PinningData.h b/src/AppInstallerRepositoryCore/Public/winget/PinningData.h @@ -79,7 +79,7 @@ namespace AppInstaller::Pinning // Gets the latest available package version that fits within the pinning restrictions. // This should be the package object that contains available versions associated with the installed version for which this evaluator was created. - std::shared_ptr<AppInstaller::Repository::IPackageVersion> GetLatestAvailableVersionForPins(const std::shared_ptr<AppInstaller::Repository::IPackage>& package); + std::shared_ptr<AppInstaller::Repository::IPackageVersion> GetLatestAvailableVersionForPins(const std::shared_ptr<AppInstaller::Repository::IPackageVersionCollection>& package); // Determines if the given version is an update to the installed version that this object was created with. // This should be a version associated with the installed version for which this evaluator was created. diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -260,8 +260,11 @@ namespace AppInstaller::Repository // The order for the sources depends on the context. return Utility::VersionAndChannel({ Version }, { Channel }) < Utility::VersionAndChannel({ other.Version }, { other.Channel }); } - }; + // Determines if a well defined key (this one) is matched by the provided key. + // The provided key may use empty values to indicate no specific matching requirements. + bool IsMatch(const PackageVersionKey& other) const; + }; // A property of a package. enum class PackageProperty @@ -270,87 +273,43 @@ namespace AppInstaller::Repository Name, }; - // Defines the installed status check type. - enum class InstalledStatusType : uint32_t - { - // None is checked. - None = 0x0, - // Check Apps and Features entry. - AppsAndFeaturesEntry = 0x0001, - // Check Apps and Features entry install location if applicable. - AppsAndFeaturesEntryInstallLocation = 0x0002, - // Check Apps and Features entry install location with installed files if applicable. - AppsAndFeaturesEntryInstallLocationFile = 0x0004, - // Check default install location if applicable. - DefaultInstallLocation = 0x0008, - // Check default install location with installed files if applicable. - DefaultInstallLocationFile = 0x0010, - - // Below are helper values for calling CheckInstalledStatus as input. - // AppsAndFeaturesEntry related checks - AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, - // DefaultInstallLocation related checks - AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, - // All checks - AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, - }; - - DEFINE_ENUM_FLAG_OPERATORS(InstalledStatusType); - - // Struct representing an individual installed status. - struct InstalledStatus - { - // The installed status type. - InstalledStatusType Type = InstalledStatusType::None; - // The installed status path. - Utility::NormalizedString Path; - // The installed status result. - HRESULT Status; - - InstalledStatus(InstalledStatusType type, Utility::NormalizedString path, HRESULT status) : - Type(type), Path(std::move(path)), Status(status) {} - }; - - // Struct representing installed status from an installer. - struct InstallerInstalledStatus - { - Manifest::ManifestInstaller Installer; - std::vector<InstalledStatus> Status; - }; - // To allow for runtime casting from IPackage to the specific types, this enum contains all of the IPackage implementations. enum class IPackageType { TestPackage, - RestAvailablePackage, - SQLiteAvailablePackage, - SQLiteInstalledPackage, + RestPackage, + SQLitePackage, PinnablePackage, - CompositePackage, + CompositeInstalledPackage, }; - // A package, potentially containing information about it's local state and the available versions. - struct IPackage + // Contains a collection of package versions. + struct IPackageVersionCollection { - virtual ~IPackage() = default; + virtual ~IPackageVersionCollection() = 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. + // Gets all versions of this package. // The versions will be returned in sorted, descending order. // Ex. { 4, 3, 2, 1 } - // The list may contain versions from multiple sources. - virtual std::vector<PackageVersionKey> GetAvailableVersionKeys() const = 0; + virtual std::vector<PackageVersionKey> GetVersionKeys() const = 0; // Gets a specific version of this package. - virtual std::shared_ptr<IPackageVersion> GetLatestAvailableVersion() const = 0; + virtual std::shared_ptr<IPackageVersion> GetVersion(const PackageVersionKey& versionKey) const = 0; - // Gets a specific version of this package. - virtual std::shared_ptr<IPackageVersion> GetAvailableVersion(const PackageVersionKey& versionKey) const = 0; + // A convenience method to effectively call `GetVersion(GetVersionKeys[0])`. + virtual std::shared_ptr<IPackageVersion> GetLatestVersion() const = 0; + }; + + // Contains information about a package and its versions from a single source. + struct IPackage : public IPackageVersionCollection + { + virtual ~IPackage() = default; + + // Gets a property of this package. + virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; + + // Gets the source that this package is from. + virtual Source GetSource() const = 0; // Determines if the given IPackage refers to the same package as this one. virtual bool IsSame(const IPackage*) const = 0; @@ -359,6 +318,22 @@ namespace AppInstaller::Repository virtual const void* CastTo(IPackageType type) const = 0; }; + // Contains information about the graph of packages related to a search. + struct ICompositePackage + { + virtual ~ICompositePackage() = default; + + // Gets a property of this package result. + virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; + + // Gets the installed package information. + virtual std::shared_ptr<IPackage> GetInstalled() = 0; + + // Gets all of the available packages for this result. + // There will be at most one package per source in this list. + virtual std::vector<std::shared_ptr<IPackage>> GetAvailable() = 0; + }; + // Does the equivalent of a dynamic_cast, but without it to allow RTTI to be disabled. // Example usage: // bool IsSame(const IPackage* other) const override @@ -382,12 +357,12 @@ namespace AppInstaller::Repository struct ResultMatch { // The package found by the search request. - std::shared_ptr<IPackage> Package; + std::shared_ptr<ICompositePackage> 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)) {} + ResultMatch(std::shared_ptr<ICompositePackage> p, PackageMatchFilter f) : Package(std::move(p)), MatchCriteria(std::move(f)) {} }; // Search result data. @@ -433,7 +408,4 @@ namespace AppInstaller::Repository private: mutable std::string m_whatMessage; }; - - // Checks installed status of a package. - std::vector<InstallerInstalledStatus> CheckPackageInstalledStatus(const std::shared_ptr<IPackage>& package, InstalledStatusType checkTypes = InstalledStatusType::AllChecks); } diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h @@ -220,6 +220,11 @@ namespace AppInstaller::Repository // To avoid putting try catch everywhere, we use bool operator here. operator bool() const; + // Determines if the sources are equivalent. + // Currently only works for individual sources, not composites. + bool operator==(const Source& other) const; + bool operator!=(const Source& other) 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, diff --git a/src/AppInstallerRepositoryCore/RepositorySearch.cpp b/src/AppInstallerRepositoryCore/RepositorySearch.cpp @@ -92,6 +92,14 @@ namespace AppInstaller::Repository } } + bool PackageVersionKey::IsMatch(const PackageVersionKey& other) const + { + return + ((other.SourceId.empty() || other.SourceId == SourceId) && + (other.Version.empty() || Utility::ICUCaseInsensitiveEquals(other.Version, Version)) && + (other.Channel.empty() || Utility::ICUCaseInsensitiveEquals(other.Channel, Channel))); + } + const char* UnsupportedRequestException::what() const noexcept { if (m_whatMessage.empty()) diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -473,6 +473,19 @@ namespace AppInstaller::Repository } } + bool Source::operator==(const Source& other) const + { + SourceDetails thisDetails = GetDetails(); + SourceDetails otherDetails = other.GetDetails(); + + return (thisDetails.Type == otherDetails.Type && thisDetails.Identifier == otherDetails.Identifier); + } + + bool Source::operator!=(const Source& other) const + { + return !operator==(other); + } + std::string Source::GetIdentifier() const { if (m_source) diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp @@ -30,11 +30,11 @@ namespace AppInstaller::Repository::Rest }; // The IPackage implementation for Available packages from RestSource. - struct AvailablePackage : public std::enable_shared_from_this<AvailablePackage>, public SourceReference, public IPackage + struct RestPackage : public std::enable_shared_from_this<RestPackage>, public SourceReference, public IPackage, public ICompositePackage { - static constexpr IPackageType PackageType = IPackageType::RestAvailablePackage; + static constexpr IPackageType PackageType = IPackageType::RestPackage; - AvailablePackage(const std::shared_ptr<RestSource>& source, IRestClient::Package&& package) : + RestPackage(const std::shared_ptr<RestSource>& source, IRestClient::Package&& package) : SourceReference(source), m_package(std::move(package)) { SortVersionsInternal(); @@ -54,12 +54,7 @@ namespace AppInstaller::Repository::Rest } } - std::shared_ptr<IPackageVersion> GetInstalledVersion() const override - { - return {}; - } - - std::vector<PackageVersionKey> GetAvailableVersionKeys() const override + std::vector<PackageVersionKey> GetVersionKeys() const override { std::shared_ptr<const RestSource> source = GetReferenceSource(); std::scoped_lock versionsLock{ m_packageVersionsLock }; @@ -74,22 +69,27 @@ namespace AppInstaller::Repository::Rest return result; } - std::shared_ptr<IPackageVersion> GetLatestAvailableVersion() const override + std::shared_ptr<IPackageVersion> GetLatestVersion() const override { std::scoped_lock versionsLock{ m_packageVersionsLock }; return GetLatestVersionInternal(); } - std::shared_ptr<IPackageVersion> GetAvailableVersion(const PackageVersionKey& versionKey) const override; + std::shared_ptr<IPackageVersion> GetVersion(const PackageVersionKey& versionKey) const override; + + Source GetSource() const override + { + return Source{ GetReferenceSource() }; + } bool IsSame(const IPackage* other) const override { - const AvailablePackage* otherAvailablePackage = PackageCast<const AvailablePackage*>(other); + const RestPackage* otherPackage = PackageCast<const RestPackage*>(other); - if (otherAvailablePackage) + if (otherPackage) { - return GetReferenceSource()->IsSame(otherAvailablePackage->GetReferenceSource().get()) && - Utility::CaseInsensitiveEquals(m_package.PackageInformation.PackageIdentifier, otherAvailablePackage->m_package.PackageInformation.PackageIdentifier); + return GetReferenceSource()->IsSame(otherPackage->GetReferenceSource().get()) && + Utility::CaseInsensitiveEquals(m_package.PackageInformation.PackageIdentifier, otherPackage->m_package.PackageInformation.PackageIdentifier); } return false; @@ -105,6 +105,17 @@ namespace AppInstaller::Repository::Rest return nullptr; } + // Inherited via ICompositePackage + std::shared_ptr<IPackage> GetInstalled() override + { + return {}; + } + + std::vector<std::shared_ptr<IPackage>> GetAvailable() override + { + return std::vector<std::shared_ptr<IPackage>>{ shared_from_this() }; + } + // Helpers for PackageVersion interop const IRestClient::PackageInfo& PackageInfo() const { @@ -161,9 +172,9 @@ namespace AppInstaller::Repository::Rest } private: - std::shared_ptr<AvailablePackage> NonConstSharedFromThis() const + std::shared_ptr<RestPackage> NonConstSharedFromThis() const { - return const_cast<AvailablePackage*>(this)->shared_from_this(); + return const_cast<RestPackage*>(this)->shared_from_this(); } // Must hold m_packageVersionsLock while calling this @@ -188,7 +199,7 @@ namespace AppInstaller::Repository::Rest struct PackageVersion : public SourceReference, public IPackageVersion { PackageVersion( - const std::shared_ptr<RestSource>& source, std::shared_ptr<AvailablePackage>&& package, IRestClient::VersionInfo versionInfo) + const std::shared_ptr<RestSource>& source, std::shared_ptr<RestPackage>&& package, IRestClient::VersionInfo versionInfo) : SourceReference(source), m_package(std::move(package)), m_versionInfo(std::move(versionInfo)) {} // Inherited via IPackageVersion @@ -347,11 +358,11 @@ namespace AppInstaller::Repository::Rest } private: - std::shared_ptr<AvailablePackage> m_package; + std::shared_ptr<RestPackage> m_package; IRestClient::VersionInfo m_versionInfo; }; - std::shared_ptr<IPackageVersion> AvailablePackage::GetAvailableVersion(const PackageVersionKey& versionKey) const + std::shared_ptr<IPackageVersion> RestPackage::GetVersion(const PackageVersionKey& versionKey) const { std::shared_ptr<RestSource> source = GetReferenceSource(); std::scoped_lock versionsLock{ m_packageVersionsLock }; @@ -405,7 +416,7 @@ namespace AppInstaller::Repository::Rest return packageVersion; } - std::shared_ptr<IPackageVersion> AvailablePackage::GetLatestVersionInternal() const + std::shared_ptr<IPackageVersion> RestPackage::GetLatestVersionInternal() const { return std::make_shared<PackageVersion>(GetReferenceSource(), NonConstSharedFromThis(), m_package.Versions.front()); } @@ -450,7 +461,7 @@ namespace AppInstaller::Repository::Rest std::shared_ptr<RestSource> sharedThis = NonConstSharedFromThis(); for (auto& result : results.Matches) { - std::shared_ptr<IPackage> package = std::make_shared<AvailablePackage>(sharedThis, std::move(result)); + std::shared_ptr<ICompositePackage> package = std::make_shared<RestPackage>(sharedThis, std::move(result)); // TODO: Improve to use Package match filter to return relevant search results. PackageMatchFilter packageFilter{ {}, {}, {} }; diff --git a/src/AppInstallerSharedLib/Yaml.cpp b/src/AppInstallerSharedLib/Yaml.cpp @@ -407,14 +407,17 @@ namespace AppInstaller::YAML std::optional<int64_t> Node::try_as_dispatch(int64_t*) const { - try - { - return std::optional{ std::stoll(m_scalar) }; - } - catch(...) + const char* begin = m_scalar.c_str(); + char* end = nullptr; + errno = 0; + int64_t result = static_cast<int64_t>(strtoll(begin, &end, 0)); + + if (errno == ERANGE || static_cast<size_t>(end - begin) != m_scalar.length()) { return {}; } + + return result; } int Node::as_dispatch(int*) const diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -1,127 +1,130 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include <mutex> -#include <winget/RepositorySource.h> -#include "CatalogPackage.h" -#include "CatalogPackage.g.cpp" -#include "PackageCatalog.h" -#include "PackageVersionInfo.h" -#include "PackageVersionId.h" -#include "PackageInstallerInstalledStatus.h" -#include "CheckInstalledStatusResult.h" -#include <wil\cppwinrt_wrl.h> -#include <winget/PinningData.h> - - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void CatalogPackage::Initialize( - ::AppInstaller::Repository::Source source, - std::shared_ptr<::AppInstaller::Repository::IPackage> package) - { - m_source = std::move(source); - m_package = std::move(package); - } - hstring CatalogPackage::Id() - { - return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Id).get()); - } - hstring CatalogPackage::Name() - { - return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Name)); - } - Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::InstalledVersion() - { - std::call_once(m_installedVersionOnceFlag, - [&]() - { - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> installedVersion = m_package.get()->GetInstalledVersion(); - if (installedVersion) - { - auto installedVersionImpl = winrt::make_self<wil::details::module_count_wrapper< - winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>>(); - installedVersionImpl->Initialize(std::move(installedVersion)); - m_installedVersion = *installedVersionImpl; - } - }); - return m_installedVersion; - } - Windows::Foundation::Collections::IVectorView<Microsoft::Management::Deployment::PackageVersionId> CatalogPackage::AvailableVersions() - { - std::call_once(m_availableVersionsOnceFlag, - [&]() - { - // Vector hasn't been populated yet. - for (auto const& versionKey : m_package.get()->GetAvailableVersionKeys()) - { - auto packageVersionId = winrt::make_self<wil::details::module_count_wrapper< - winrt::Microsoft::Management::Deployment::implementation::PackageVersionId>>(); - packageVersionId->Initialize(versionKey); - m_availableVersions.Append(*packageVersionId); - } - }); - return m_availableVersions.GetView(); - } - - void CatalogPackage::InitializeDefaultInstallVersion() - { - std::call_once(m_defaultInstallVersionOnceFlag, - [&]() - { - using namespace AppInstaller::Pinning; - - PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, m_package->GetInstalledVersion()); - - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> latestVersion = evaluator.GetLatestAvailableVersionForPins(m_package); - if (latestVersion) - { - m_updateAvailable = evaluator.IsUpdate(latestVersion); - - // DefaultInstallVersion hasn't been created yet, create and populate it. - // DefaultInstallVersion is the LatestAvailableVersion of the internal package object. - auto latestVersionImpl = winrt::make_self<wil::details::module_count_wrapper< - winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>>(); - latestVersionImpl->Initialize(std::move(latestVersion)); - m_defaultInstallVersion = *latestVersionImpl; - } - }); - } - - Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::DefaultInstallVersion() - { - InitializeDefaultInstallVersion(); - return m_defaultInstallVersion; - } - - Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::GetPackageVersionInfo(Microsoft::Management::Deployment::PackageVersionId const& versionKey) - { - winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; - - ::AppInstaller::Repository::PackageVersionKey internalVersionKey(winrt::to_string(versionKey.PackageCatalogId()), winrt::to_string(versionKey.Version()), winrt::to_string(versionKey.Channel())); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = m_package.get()->GetAvailableVersion(internalVersionKey); - if (availableVersion) - { - auto packageVersionInfoImpl = winrt::make_self<wil::details::module_count_wrapper< - winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>>(); - packageVersionInfoImpl->Initialize(std::move(availableVersion)); - packageVersionInfo =*packageVersionInfoImpl; - } - return packageVersionInfo; - } - - bool CatalogPackage::IsUpdateAvailable() - { - InitializeDefaultInstallVersion(); - return m_updateAvailable; - } - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include <mutex> +#include <winget/RepositorySource.h> +#include "CatalogPackage.h" +#include "CatalogPackage.g.cpp" +#include "PackageCatalog.h" +#include "PackageVersionInfo.h" +#include "PackageVersionId.h" +#include "PackageInstallerInstalledStatus.h" +#include "CheckInstalledStatusResult.h" +#include <wil\cppwinrt_wrl.h> +#include <winget/PinningData.h> +#include <winget/PackageVersionSelection.h> + + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void CatalogPackage::Initialize( + ::AppInstaller::Repository::Source source, + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> package) + { + m_source = std::move(source); + m_package = std::move(package); + } + hstring CatalogPackage::Id() + { + return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Id).get()); + } + hstring CatalogPackage::Name() + { + return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Name)); + } + Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::InstalledVersion() + { + std::call_once(m_installedVersionOnceFlag, + [&]() + { + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> installedVersion = GetInstalledVersion(m_package); + if (installedVersion) + { + auto installedVersionImpl = winrt::make_self<wil::details::module_count_wrapper< + winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>>(); + installedVersionImpl->Initialize(std::move(installedVersion)); + m_installedVersion = *installedVersionImpl; + } + }); + return m_installedVersion; + } + Windows::Foundation::Collections::IVectorView<Microsoft::Management::Deployment::PackageVersionId> CatalogPackage::AvailableVersions() + { + std::call_once(m_availableVersionsOnceFlag, + [&]() + { + // Vector hasn't been populated yet. + for (auto const& versionKey : ::AppInstaller::Repository::GetAllAvailableVersions(m_package)->GetVersionKeys()) + { + auto packageVersionId = winrt::make_self<wil::details::module_count_wrapper< + winrt::Microsoft::Management::Deployment::implementation::PackageVersionId>>(); + packageVersionId->Initialize(versionKey); + m_availableVersions.Append(*packageVersionId); + } + }); + return m_availableVersions.GetView(); + } + + void CatalogPackage::InitializeDefaultInstallVersion() + { + std::call_once(m_defaultInstallVersionOnceFlag, + [&]() + { + using namespace AppInstaller::Pinning; + + PinningData pinningData{ PinningData::Disposition::ReadOnly }; + auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, GetInstalledVersion(m_package)); + + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> latestVersion = + evaluator.GetLatestAvailableVersionForPins(::AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(m_package)); + if (latestVersion) + { + m_updateAvailable = evaluator.IsUpdate(latestVersion); + + // DefaultInstallVersion hasn't been created yet, create and populate it. + // DefaultInstallVersion is the LatestAvailableVersion of the internal package object. + auto latestVersionImpl = winrt::make_self<wil::details::module_count_wrapper< + winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>>(); + latestVersionImpl->Initialize(std::move(latestVersion)); + m_defaultInstallVersion = *latestVersionImpl; + } + }); + } + + Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::DefaultInstallVersion() + { + InitializeDefaultInstallVersion(); + return m_defaultInstallVersion; + } + + Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::GetPackageVersionInfo(Microsoft::Management::Deployment::PackageVersionId const& versionKey) + { + winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; + + ::AppInstaller::Repository::PackageVersionKey internalVersionKey(winrt::to_string(versionKey.PackageCatalogId()), winrt::to_string(versionKey.Version()), winrt::to_string(versionKey.Channel())); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = + ::AppInstaller::Repository::GetAllAvailableVersions(m_package)->GetVersion(internalVersionKey); + if (availableVersion) + { + auto packageVersionInfoImpl = winrt::make_self<wil::details::module_count_wrapper< + winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo>>(); + packageVersionInfoImpl->Initialize(std::move(availableVersion)); + packageVersionInfo =*packageVersionInfoImpl; + } + return packageVersionInfo; + } + + bool CatalogPackage::IsUpdateAvailable() + { + InitializeDefaultInstallVersion(); + return m_updateAvailable; + } + Windows::Foundation::IAsyncOperation<winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult> CatalogPackage::CheckInstalledStatusAsync( Microsoft::Management::Deployment::InstalledStatusType checkTypes) { co_return CheckInstalledStatus(checkTypes); - } + } Microsoft::Management::Deployment::CheckInstalledStatusResult CatalogPackage::CheckInstalledStatus( Microsoft::Management::Deployment::InstalledStatusType checkTypes) { @@ -152,17 +155,17 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::implementation::CheckInstalledStatusResult>>(); checkInstalledStatusResult->Initialize(status, installedStatus); return *checkInstalledStatusResult; - } + } winrt::Windows::Foundation::IAsyncOperation<winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult> CatalogPackage::CheckInstalledStatusAsync() { co_return CheckInstalledStatus(InstalledStatusType::AllChecks); - } + } winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult CatalogPackage::CheckInstalledStatus() { return CheckInstalledStatus(InstalledStatusType::AllChecks); - } - std::shared_ptr<::AppInstaller::Repository::IPackage> CatalogPackage::GetRepositoryPackage() - { - return m_package; - } -} + } + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> CatalogPackage::GetRepositoryPackage() + { + return m_package; + } +} diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.h b/src/Microsoft.Management.Deployment/CatalogPackage.h @@ -12,8 +12,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Initialize( ::AppInstaller::Repository::Source source, - std::shared_ptr<::AppInstaller::Repository::IPackage> package); - std::shared_ptr<::AppInstaller::Repository::IPackage> GetRepositoryPackage(); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> package); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> GetRepositoryPackage(); #endif hstring Id(); @@ -34,7 +34,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: ::AppInstaller::Repository::Source m_source; - std::shared_ptr<::AppInstaller::Repository::IPackage> m_package; + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> m_package; bool m_updateAvailable = false; 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/InstalledStatus.h b/src/Microsoft.Management.Deployment/InstalledStatus.h @@ -3,6 +3,7 @@ #pragma once #include "InstalledStatus.g.h" #include <winget/RepositorySearch.h> +#include <winget/InstalledStatus.h> namespace winrt::Microsoft::Management::Deployment::implementation { diff --git a/src/Microsoft.Management.Deployment/PackageInstallerInstalledStatus.h b/src/Microsoft.Management.Deployment/PackageInstallerInstalledStatus.h @@ -3,6 +3,7 @@ #pragma once #include "PackageInstallerInstalledStatus.g.h" #include <winget/RepositorySearch.h> +#include <winget/InstalledStatus.h> #include <winrt/Windows.Foundation.Collections.h> namespace winrt::Microsoft::Management::Deployment::implementation diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -676,7 +676,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation // Add Package which is used by RecordUninstall later for removing from tracking catalog of correlated available sources as best effort winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self<winrt::Microsoft::Management::Deployment::implementation::CatalogPackage>(package); - std::shared_ptr<::AppInstaller::Repository::IPackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); comContext->Add<AppInstaller::CLI::Execution::Data::Package>(internalPackage); return Execution::OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext));