commit 02e14be0a04d618c1c769d166dae185d8b9b58a1 parent 9e1022c63de2e7a9836d5a37fb84a73b485043d0 Author: JohnMcPMS <johnmcp@microsoft.com> Date: Mon, 26 Oct 2020 12:25:46 -0700 Implement manifest metadata in index (#626) In order to store arbitrary amounts of data about installed packages, we need a more flexible table. It isn't needed for available packages (yet), and so is only created on first write. The data that is stored is { manifest rowid, metadata enum, value string }. This part is very straightforward. In order for the installed package enumeration for `list` et al. to be able to populate this, I realized it needed to be an enum stored in the repository code (sorry @yao-msft). So part of this change is moving everything over to that. In addition, I fixed some issues while I was around the code changing to an enum. Finally, I hooked up the current MSIX enumeration to the only metadata item currently in use, and the SQLiteIndex source to read the metadata rather than just return an empty result every time. Diffstat:
35 files changed, 604 insertions(+), 204 deletions(-)
diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp b/src/AppInstallerCLICore/Workflows/ManifestComparator.cpp @@ -9,86 +9,125 @@ using namespace AppInstaller::Manifest; namespace AppInstaller::CLI::Workflow { - bool InstallerComparator::operator() (const ManifestInstaller& installer1, const ManifestInstaller& installer2) + namespace { - // Applicable architecture should always come before inapplicable architecture - if (Utility::IsApplicableArchitecture(installer1.Arch) != Utility::InapplicableArchitecture && - Utility::IsApplicableArchitecture(installer2.Arch) == Utility::InapplicableArchitecture) + // Determine if the installer is applicable. + bool IsInstallerApplicable(const Manifest::ManifestInstaller& installer, Manifest::ManifestInstaller::InstallerTypeEnum installedType) { + if (Utility::IsApplicableArchitecture(installer.Arch) == Utility::InapplicableArchitecture) + { + return false; + } + + if (installedType != Manifest::ManifestInstaller::InstallerTypeEnum::Unknown && + !Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer.InstallerType, installedType)) + { + return false; + } + return true; } - // If there's installation metadata, pick the preferred one or compatible one - auto installerTypeItr = m_installationMetadata.find(s_InstallationMetadata_Key_InstallerType); - if (installerTypeItr != m_installationMetadata.end()) + // This is used in sorting the list of available installers to get the best match. + // Determines if installer1 is a better match than installer2. + bool IsInstallerBetterMatch( + const Manifest::ManifestInstaller& installer1, + const Manifest::ManifestInstaller& installer2, + Manifest::ManifestInstaller::InstallerTypeEnum installedType) { - auto installerType = Manifest::ManifestInstaller::ConvertToInstallerTypeEnum(installerTypeItr->second); - if (installer1.InstallerType == installerType && installer2.InstallerType != installerType) + auto arch1 = Utility::IsApplicableArchitecture(installer1.Arch); + auto arch2 = Utility::IsApplicableArchitecture(installer2.Arch); + + // Applicable architecture should always come before inapplicable architecture + if (arch1 != Utility::InapplicableArchitecture && + arch2 == Utility::InapplicableArchitecture) { return true; } - if (Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer1.InstallerType, installerType) && - !Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer2.InstallerType, installerType)) + + // If there's installation metadata, pick the preferred one or compatible one + if (installedType != Manifest::ManifestInstaller::InstallerTypeEnum::Unknown) + { + if (installer1.InstallerType == installedType && installer2.InstallerType != installedType) + { + return true; + } + if (Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer1.InstallerType, installedType) && + !Manifest::ManifestInstaller::IsInstallerTypeCompatible(installer2.InstallerType, installedType)) + { + return true; + } + } + + // Todo: Compare only architecture for now. Need more work and spec. + if (arch1 > arch2) { return true; } - } - // Todo: Compare only architecture for now. Need more work and spec. - if (Utility::IsApplicableArchitecture(installer1.Arch) > Utility::IsApplicableArchitecture(installer2.Arch)) - { - return true; + return false; } - return false; - } - - bool LocalizationComparator::operator() (const ManifestLocalization& loc1, const ManifestLocalization& loc2) - { - // Todo: Compare simple language for now. Need more work and spec. - std::string userPreferredLocale = std::locale("").name(); + // This is used in sorting the list of available localizations to get the best match. + struct LocalizationComparator + { + bool operator() ( + const Manifest::ManifestLocalization& loc1, + const Manifest::ManifestLocalization& loc2) + { + // Todo: Compare simple language for now. Need more work and spec. + std::string userPreferredLocale = std::locale("").name(); - auto foundLoc1 = userPreferredLocale.find(loc1.Language); - auto foundLoc2 = userPreferredLocale.find(loc2.Language); + auto foundLoc1 = userPreferredLocale.find(loc1.Language); + auto foundLoc2 = userPreferredLocale.find(loc2.Language); - if (foundLoc1 != std::string::npos && foundLoc2 == std::string::npos) - { - return true; - } + if (foundLoc1 != std::string::npos && foundLoc2 == std::string::npos) + { + return true; + } - return false; + return false; + } + }; } std::optional<Manifest::ManifestInstaller> ManifestComparator::GetPreferredInstaller(const Manifest::Manifest& manifest) { AICLI_LOG(CLI, Info, << "Starting installer selection."); - // Sorting the list of available installers according to rules defined in InstallerComparator. - auto installers = manifest.Installers; - std::sort(installers.begin(), installers.end(), m_installerComparator); - - // If the first one's architecture is inapplicable, then no installer is applicable. - if (Utility::IsApplicableArchitecture(installers[0].Arch) == Utility::InapplicableArchitecture) + // Get the currently installed package's type (if present) + Manifest::ManifestInstaller::InstallerTypeEnum installedType = Manifest::ManifestInstaller::InstallerTypeEnum::Unknown; + auto installerTypeItr = m_installationMetadata.find(Repository::PackageVersionMetadata::InstalledType); + if (installerTypeItr != m_installationMetadata.end()) { - return {}; + installedType = Manifest::ManifestInstaller::ConvertToInstallerTypeEnum(installerTypeItr->second); } - // If the first one's InstallerType is inapplicable, then no installer is applicable. - auto installerTypeItr = m_installationMetadata.find(s_InstallationMetadata_Key_InstallerType); - if (installerTypeItr != m_installationMetadata.end()) + const Manifest::ManifestInstaller* result = nullptr; + + for (const auto& installer : manifest.Installers) { - auto installerType = Manifest::ManifestInstaller::ConvertToInstallerTypeEnum(installerTypeItr->second); - if (!Manifest::ManifestInstaller::IsInstallerTypeCompatible(installers[0].InstallerType, installerType)) + if (!result) + { + if (IsInstallerApplicable(installer, installedType)) + { + result = &installer; + } + } + else if (IsInstallerBetterMatch(installer, *result, installedType)) { - return {}; + result = &installer; } } - ManifestInstaller& selectedInstaller = installers[0]; + if (!result) + { + return {}; + } - Logging::Telemetry().LogSelectedInstaller((int)selectedInstaller.Arch, selectedInstaller.Url, Manifest::ManifestInstaller::InstallerTypeToString(selectedInstaller.InstallerType), selectedInstaller.Scope, selectedInstaller.Language); + Logging::Telemetry().LogSelectedInstaller(static_cast<int>(result->Arch), result->Url, Manifest::ManifestInstaller::InstallerTypeToString(result->InstallerType), result->Scope, result->Language); - return std::move(selectedInstaller); + return *result; } Manifest::ManifestLocalization ManifestComparator::GetPreferredLocalization(const Manifest::Manifest& manifest) @@ -101,7 +140,7 @@ namespace AppInstaller::CLI::Workflow if (!manifest.Localization.empty()) { auto localization = manifest.Localization; - std::sort(localization.begin(), localization.end(), m_localizationComparator); + std::sort(localization.begin(), localization.end(), LocalizationComparator()); // TODO: needs to check language applicability here diff --git a/src/AppInstallerCLICore/Workflows/ManifestComparator.h b/src/AppInstallerCLICore/Workflows/ManifestComparator.h @@ -3,48 +3,23 @@ #pragma once #include "ExecutionArgs.h" #include <winget/Manifest.h> - -#include <optional> +#include <AppInstallerRepositorySearch.h> namespace AppInstaller::CLI::Workflow { - // This is used in sorting the list of available installers to get the best match. - struct InstallerComparator - { - InstallerComparator(const std::map<std::string, std::string>& installationMetadata) : - m_installationMetadata(installationMetadata) {} - - bool operator() ( - const Manifest::ManifestInstaller& installer1, - const Manifest::ManifestInstaller& installer2); - - private: - const std::map<std::string, std::string>& m_installationMetadata; - }; - - // This is used in sorting the list of available localizations to get the best match. - struct LocalizationComparator - { - bool operator() ( - const Manifest::ManifestLocalization& loc1, - const Manifest::ManifestLocalization& loc2); - }; - // Class in charge of comparing manifest entries struct ManifestComparator { - ManifestComparator(const Execution::Args&, const std::map<std::string, std::string>& installationMetadata = {}) : - m_installationMetadata(installationMetadata), m_installerComparator(installationMetadata) {} + ManifestComparator(const Execution::Args&, Repository::IPackageVersion::Metadata installationMetadata = {}) : + m_installationMetadata(std::move(installationMetadata)) {} std::optional<Manifest::ManifestInstaller> GetPreferredInstaller(const Manifest::Manifest& manifest); Manifest::ManifestLocalization GetPreferredLocalization(const Manifest::Manifest& manifest); private: // TODO: Handle args to change how we select. - const std::map<std::string, std::string>& m_installationMetadata; - LocalizationComparator m_localizationComparator; - InstallerComparator m_installerComparator; + Repository::IPackageVersion::Metadata m_installationMetadata; }; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -13,36 +13,17 @@ namespace AppInstaller::CLI::Workflow { namespace { - bool IsUpdateVersionApplicable(Execution::Context& context, const Utility::Version& updateVersion) + bool IsUpdateVersionApplicable(const Utility::Version& installedVersion, const Utility::Version& updateVersion) { - const auto& installedPackage = context.Get<Execution::Data::InstalledPackageVersion>(); - const auto& installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); - - bool updateApplicable = false; - if (updateVersion > installedVersion) - { - updateApplicable = true; - } - else if (updateVersion == installedVersion) - { - // If installer type is MSStore, we'll let Store api to handle updates later - const auto& installationMetadata = installedPackage->GetInstallationMetadata(); - auto installerTypeItr = installationMetadata.find(s_InstallationMetadata_Key_InstallerType); - if (installerTypeItr != installationMetadata.end() && - Manifest::ManifestInstaller::InstallerTypeEnum::MSStore == Manifest::ManifestInstaller::ConvertToInstallerTypeEnum(installerTypeItr->second)) - { - updateApplicable = true; - } - } - - return updateApplicable; + return (installedVersion < updateVersion || updateVersion.IsLatest()); } } void SelectLatestApplicableUpdate::operator()(Execution::Context& context) const { - const auto& installationMetadata = context.Get<Execution::Data::InstalledPackageVersion>()->GetInstallationMetadata(); - ManifestComparator manifestComparator(context.Args, installationMetadata); + auto installedPackage = context.Get<Execution::Data::InstalledPackageVersion>(); + Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); + ManifestComparator manifestComparator(context.Args, installedPackage->GetMetadata()); bool updateFound = false; // The version keys should have already been sorted by version @@ -50,7 +31,7 @@ namespace AppInstaller::CLI::Workflow for (const auto& key : versionKeys) { // Check Update Version - if (IsUpdateVersionApplicable(context, Utility::Version(key.Version))) + if (IsUpdateVersionApplicable(installedVersion, Utility::Version(key.Version))) { auto manifest = m_package.GetAvailableVersion(key)->GetManifest(); @@ -90,9 +71,11 @@ namespace AppInstaller::CLI::Workflow void EnsureUpdateVersionApplicable(Execution::Context& context) { + auto installedPackage = context.Get<Execution::Data::InstalledPackageVersion>(); + Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); Utility::Version updateVersion(context.Get<Execution::Data::Manifest>().Version); - if (!IsUpdateVersionApplicable(context, updateVersion)) + if (!IsUpdateVersionApplicable(installedVersion, updateVersion)) { context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -528,13 +528,13 @@ namespace AppInstaller::CLI::Workflow { bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); - std::map<std::string, std::string> installationMetadata; + IPackageVersion::Metadata installationMetadata; if (isUpdate) { - installationMetadata = context.Get<Execution::Data::InstalledPackageVersion>()->GetInstallationMetadata(); + installationMetadata = context.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata(); } - ManifestComparator manifestComparator(context.Args, installationMetadata); + ManifestComparator manifestComparator(context.Args, std::move(installationMetadata)); context.Add<Execution::Data::Installer>(manifestComparator.GetPreferredInstaller(context.Get<Execution::Data::Manifest>())); } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -16,8 +16,6 @@ namespace AppInstaller::CLI::Execution namespace AppInstaller::CLI::Workflow { - static const char* s_InstallationMetadata_Key_InstallerType = "InstallerType"; - // Values are ordered in a typical workflow stages enum class ExecutionStage : uint32_t { diff --git a/src/AppInstallerCLIE2ETests/TestCommon.cs b/src/AppInstallerCLIE2ETests/TestCommon.cs @@ -156,9 +156,32 @@ namespace AppInstallerCLIE2ETests RunCommandResult result = new RunCommandResult(); - result.ExitCode = File.Exists(exitCodeFile) ? int.Parse(File.ReadAllText(exitCodeFile).Trim()) : unchecked((int)0x80004005); - result.StdOut = File.Exists(stdOutFile) ? File.ReadAllText(stdOutFile) : ""; - result.StdErr = File.Exists(stdErrFile) ? File.ReadAllText(stdErrFile) : ""; + // Sometimes the files are still in use; allow for this with a wait and retry loop. + for (int retryCount = 0; retryCount < 4; ++retryCount) + { + bool success = false; + + try + { + result.ExitCode = File.Exists(exitCodeFile) ? int.Parse(File.ReadAllText(exitCodeFile).Trim()) : unchecked((int)0x80004005); + result.StdOut = File.Exists(stdOutFile) ? File.ReadAllText(stdOutFile) : ""; + result.StdErr = File.Exists(stdErrFile) ? File.ReadAllText(stdErrFile) : ""; + success = true; + } + catch (Exception e) + { + TestContext.Out.WriteLine("Failed to access files: " + e.Message); + } + + if (success) + { + break; + } + else + { + Thread.Sleep(250); + } + } return result; } diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp @@ -86,7 +86,7 @@ std::shared_ptr<TestPackage> MakeInstalled(std::function<void(Manifest::Manifest { Manifest::Manifest manifest = MakeDefaultManifest(); op(manifest); - return TestPackage::Make(manifest, TestPackage::InstallationMetadataMap{}); + return TestPackage::Make(manifest, TestPackage::MetadataMap{}); } std::shared_ptr<TestPackage> MakeAvailable(std::function<void(Manifest::Manifest&)> op) diff --git a/src/AppInstallerCLITests/SQLiteIndex.cpp b/src/AppInstallerCLITests/SQLiteIndex.cpp @@ -181,6 +181,12 @@ bool ArePackageFamilyNameAndProductCodeSupported(const SQLiteIndex& index, const return (index.GetVersion() >= Schema::Version{ 1, 1 } && testVersion >= Schema::Version{ 1, 1 }); } +bool IsManifestMetadataSupported(const SQLiteIndex& index, const Schema::Version& testVersion) +{ + UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); + return (index.GetVersion() >= Schema::Version{ 1, 1 } && testVersion >= Schema::Version{ 1, 1 }); +} + std::string GetPropertyStringByKey(const SQLiteIndex& index, SQLite::rowid_t id, PackageVersionProperty property, std::string_view version, std::string_view channel) { auto manifestId = index.GetManifestIdByKey(id, version, channel); @@ -1925,3 +1931,47 @@ TEST_CASE("SQLiteIndex_GetMultiProperty_ProductCode", "[sqliteindex]") REQUIRE(props.empty()); } } + +TEST_CASE("SQLiteIndex_ManifestMetadata", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2", { "PFN1", "PFN2" }, {} }, + }); + + Schema::Version testVersion = TestPrepareForRead(index); + + SearchRequest request; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); + + for (const auto [id, match] : results.Matches) + { + REQUIRE(index.GetMetadataByManifestId(id).empty()); + } + + auto manifestId1 = results.Matches[0].first; + auto manifestId2 = results.Matches[1].first; + + std::string metadataValue = "data about data"; + + index.SetMetadataByManifestId(manifestId1, PackageVersionMetadata::InstalledType, metadataValue); + + if (IsManifestMetadataSupported(index, testVersion)) + { + auto metadataResult = index.GetMetadataByManifestId(manifestId1); + REQUIRE(metadataResult.size() == 1); + REQUIRE(metadataResult[0].first == PackageVersionMetadata::InstalledType); + REQUIRE(metadataResult[0].second == metadataValue); + } + else + { + REQUIRE(index.GetMetadataByManifestId(manifestId1).empty()); + } + + REQUIRE(index.GetMetadataByManifestId(manifestId2).empty()); +} diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp @@ -9,8 +9,8 @@ using namespace AppInstaller::Repository; namespace TestCommon { - TestPackageVersion::TestPackageVersion(const Manifest& manifest, InstallationMetadataMap installationMetadata) : - VersionManifest(manifest), InstallationMetadata(std::move(installationMetadata)) {} + TestPackageVersion::TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata) : + VersionManifest(manifest), Metadata(std::move(installationMetadata)) {} TestPackageVersion::LocIndString TestPackageVersion::GetProperty(PackageVersionProperty property) const { @@ -57,9 +57,9 @@ namespace TestCommon return VersionManifest; } - std::map<std::string, std::string> TestPackageVersion::GetInstallationMetadata() const + TestPackageVersion::MetadataMap TestPackageVersion::GetMetadata() const { - return InstallationMetadata; + return Metadata; } void TestPackageVersion::AddFoldedIfHasValueAndNotPresent(const Utility::NormalizedString& value, std::vector<LocIndString>& target) @@ -83,7 +83,7 @@ namespace TestCommon } } - TestPackage::TestPackage(const Manifest& installed, InstallationMetadataMap installationMetadata, const std::vector<Manifest>& available) : + TestPackage::TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector<Manifest>& available) : InstalledVersion(TestPackageVersion::Make(installed, std::move(installationMetadata))) { for (const auto& manifest : available) diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h @@ -14,9 +14,9 @@ namespace TestCommon { using Manifest = AppInstaller::Manifest::Manifest; using LocIndString = AppInstaller::Utility::LocIndString; - using InstallationMetadataMap = std::map<std::string, std::string>; + using MetadataMap = AppInstaller::Repository::IPackageVersion::Metadata; - TestPackageVersion(const Manifest& manifest, InstallationMetadataMap installationMetadata = {}); + TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata = {}); template <typename... Args> static std::shared_ptr<TestPackageVersion> Make(Args&&... args) @@ -27,10 +27,10 @@ namespace TestCommon LocIndString GetProperty(AppInstaller::Repository::PackageVersionProperty property) const override; std::vector<LocIndString> GetMultiProperty(AppInstaller::Repository::PackageVersionMultiProperty property) const override; Manifest GetManifest() const override; - InstallationMetadataMap GetInstallationMetadata() const override; + MetadataMap GetMetadata() const override; Manifest VersionManifest; - InstallationMetadataMap InstallationMetadata; + MetadataMap Metadata; protected: static void AddFoldedIfHasValueAndNotPresent(const AppInstaller::Utility::NormalizedString& value, std::vector<LocIndString>& target); @@ -41,13 +41,13 @@ namespace TestCommon { using Manifest = AppInstaller::Manifest::Manifest; using LocIndString = AppInstaller::Utility::LocIndString; - using InstallationMetadataMap = TestPackageVersion::InstallationMetadataMap; + using MetadataMap = TestPackageVersion::MetadataMap; // Create a package with only available versions using these manifests. TestPackage(const std::vector<Manifest>& available); // Create a package with an installed version, metadata, and optionally available versions. - TestPackage(const Manifest& installed, InstallationMetadataMap installationMetadata, const std::vector<Manifest>& available = {}); + TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector<Manifest>& available = {}); template <typename... Args> static std::shared_ptr<TestPackage> Make(Args&&... args) diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -111,7 +111,7 @@ namespace ResultMatch( TestPackage::Make( manifest, - TestPackage::InstallationMetadataMap{ { s_InstallationMetadata_Key_InstallerType, "Exe" } }, + TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest } ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); @@ -125,7 +125,7 @@ namespace ResultMatch( TestPackage::Make( manifest, - TestPackage::InstallationMetadataMap{ { s_InstallationMetadata_Key_InstallerType, "Msix" } }, + TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest } ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); @@ -138,7 +138,7 @@ namespace ResultMatch( TestPackage::Make( manifest, - TestPackage::InstallationMetadataMap{ { s_InstallationMetadata_Key_InstallerType, "MSStore" } }, + TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "MSStore" } }, std::vector<Manifest>{ manifest } ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMSStoreInstaller"))); @@ -152,7 +152,7 @@ namespace ResultMatch( TestPackage::Make( manifest2, - TestPackage::InstallationMetadataMap{ { s_InstallationMetadata_Key_InstallerType, "Exe" } }, + TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector<Manifest>{ manifest2, manifest } ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); @@ -166,7 +166,7 @@ namespace ResultMatch( TestPackage::Make( manifest, - TestPackage::InstallationMetadataMap{ { s_InstallationMetadata_Key_InstallerType, "Msix" } }, + TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector<Manifest>{ manifest2, manifest } ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); diff --git a/src/AppInstallerCommonCore/Architecture.cpp b/src/AppInstallerCommonCore/Architecture.cpp @@ -8,30 +8,78 @@ namespace AppInstaller::Utility { + namespace + { + void AddArchitectureIfGuestMachineSupported(std::vector<Architecture>& target, Architecture architecture, USHORT guestMachine) + { + BOOL supported = FALSE; + LOG_IF_FAILED(IsWow64GuestMachineSupported(guestMachine, &supported)); + + if (supported) + { + target.push_back(architecture); + } + } + + // Gets the applicable architectures for the current machine. + std::vector<Architecture> CreateApplicableArchitecturesVector() + { + std::vector<Architecture> applicableArchs; + + switch (GetSystemArchitecture()) + { + case Architecture::Arm64: + applicableArchs.push_back(Architecture::Arm64); + AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::Arm, IMAGE_FILE_MACHINE_ARMNT); + AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::X86, IMAGE_FILE_MACHINE_I386); + applicableArchs.push_back(Architecture::Neutral); + break; + case Architecture::Arm: + applicableArchs.push_back(Architecture::Arm); + applicableArchs.push_back(Architecture::Neutral); + break; + case Architecture::X86: + applicableArchs.push_back(Architecture::X86); + applicableArchs.push_back(Architecture::Neutral); + break; + case Architecture::X64: + applicableArchs.push_back(Architecture::X64); + AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::X86, IMAGE_FILE_MACHINE_I386); + applicableArchs.push_back(Architecture::Neutral); + break; + default: + applicableArchs.push_back(Architecture::Neutral); + } + + return applicableArchs; + } + } + Architecture ConvertToArchitectureEnum(const std::string& archStr) { - if (ToLower(archStr) == "x86") + std::string arch = ToLower(archStr); + if (arch == "x86") { return Architecture::X86; } - else if (ToLower(archStr) == "x64") + else if (arch == "x64") { return Architecture::X64; } - if (ToLower(archStr) == "arm") + else if (arch == "arm") { return Architecture::Arm; } - else if (ToLower(archStr) == "arm64") + else if (arch == "arm64") { return Architecture::Arm64; } - else if (ToLower(archStr) == "neutral") + else if (arch == "neutral") { return Architecture::Neutral; } - AICLI_LOG(YAML, Info, << "Convert to architecture enum. Unknown architecture: " << archStr); + AICLI_LOG(YAML, Info, << "ConvertToArchitectureEnum: Unknown architecture: " << archStr); return Architecture::Unknown; } @@ -46,7 +94,6 @@ namespace AppInstaller::Utility switch (systemInfo.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_AMD64: - case PROCESSOR_ARCHITECTURE_IA64: systemArchitecture = Architecture::X64; break; case PROCESSOR_ARCHITECTURE_ARM: @@ -63,46 +110,15 @@ namespace AppInstaller::Utility return systemArchitecture; } - std::vector<Architecture> GetApplicableArchitectures() + const std::vector<Architecture>& GetApplicableArchitectures() { - static std::vector<Architecture> applicableArchs; - - if (!applicableArchs.empty()) - { - return applicableArchs; - } - - switch (GetSystemArchitecture()) - { - case Architecture::Arm64: - applicableArchs.push_back(Architecture::Arm64); - applicableArchs.push_back(Architecture::Neutral); - applicableArchs.push_back(Architecture::Arm); - applicableArchs.push_back(Architecture::X86); - break; - case Architecture::Arm: - applicableArchs.push_back(Architecture::Arm); - applicableArchs.push_back(Architecture::Neutral); - break; - case Architecture::X86: - applicableArchs.push_back(Architecture::X86); - applicableArchs.push_back(Architecture::Neutral); - break; - case Architecture::X64: - applicableArchs.push_back(Architecture::X64); - applicableArchs.push_back(Architecture::Neutral); - applicableArchs.push_back(Architecture::X86); - break; - default: - applicableArchs.push_back(Architecture::Neutral); - } - + static std::vector<Architecture> applicableArchs = CreateApplicableArchitecturesVector(); return applicableArchs; } int IsApplicableArchitecture(Architecture arch) { - std::vector<Architecture> applicableArchs = GetApplicableArchitectures(); + const std::vector<Architecture>& applicableArchs = GetApplicableArchitectures(); auto it = std::find(applicableArchs.begin(), applicableArchs.end(), arch); if (it != applicableArchs.end()) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestInstaller.cpp b/src/AppInstallerCommonCore/Manifest/ManifestInstaller.cpp @@ -5,6 +5,33 @@ namespace AppInstaller::Manifest { + namespace + { + enum class CompatibilitySet + { + None, + Exe, + Msi, + }; + + CompatibilitySet GetCompatibilitySet(ManifestInstaller::InstallerTypeEnum type) + { + switch (type) + { + case ManifestInstaller::InstallerTypeEnum::Inno: + case ManifestInstaller::InstallerTypeEnum::Nullsoft: + case ManifestInstaller::InstallerTypeEnum::Exe: + case ManifestInstaller::InstallerTypeEnum::Burn: + return CompatibilitySet::Exe; + case ManifestInstaller::InstallerTypeEnum::Wix: + case ManifestInstaller::InstallerTypeEnum::Msi: + return CompatibilitySet::Msi; + default: + return CompatibilitySet::None; + } + } + } + ManifestInstaller::InstallerTypeEnum ManifestInstaller::ConvertToInstallerTypeEnum(const std::string& in) { std::string inStrLower = Utility::ToLower(in); @@ -125,27 +152,27 @@ namespace AppInstaller::Manifest bool ManifestInstaller::IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2) { + // Unknown type cannot be compatible with any other if (type1 == InstallerTypeEnum::Unknown || type2 == InstallerTypeEnum::Unknown) { return false; } - std::vector<InstallerTypeEnum> compatList1 = + // Not unknown, so must be compatible + if (type1 == type2) { - InstallerTypeEnum::Exe, - InstallerTypeEnum::Inno, - InstallerTypeEnum::Nullsoft, - InstallerTypeEnum::Burn, - }; + return true; + } + + CompatibilitySet set1 = GetCompatibilitySet(type1); + CompatibilitySet set2 = GetCompatibilitySet(type2); - std::vector<InstallerTypeEnum> compatList2 = + // If either is none, they can't be compatible + if (set1 == CompatibilitySet::None || set2 == CompatibilitySet::None) { - InstallerTypeEnum::Msi, - InstallerTypeEnum::Wix - }; + return false; + } - return type1 == type2 || - (std::find(compatList1.begin(), compatList1.end(), type1) != compatList1.end() && std::find(compatList1.begin(), compatList1.end(), type2) != compatList1.end()) || - (std::find(compatList2.begin(), compatList2.end(), type1) != compatList2.end() && std::find(compatList2.begin(), compatList2.end(), type2) != compatList2.end()); + return set1 == set2; } } diff --git a/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h b/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h @@ -24,8 +24,8 @@ namespace AppInstaller::Utility // Gets the system's architecture as Architecture enum AppInstaller::Utility::Architecture GetSystemArchitecture(); - // Gets a set of architectures that are applicable to the current system - std::vector<Architecture> GetApplicableArchitectures(); + // Gets the set of architectures that are applicable to the current system + const std::vector<Architecture>& GetApplicableArchitectures(); // Gets if an architecture is applicable to the system // Returns the priority in the applicable architecture list if the architecture is applicable. 0 has lowest priority. diff --git a/src/AppInstallerCommonCore/Public/AppInstallerLanguageUtilities.h b/src/AppInstallerCommonCore/Public/AppInstallerLanguageUtilities.h @@ -55,14 +55,14 @@ namespace AppInstaller // Get the integral value for an enum. template <typename E> - inline std::enable_if_t<std::is_enum_v<E>, std::underlying_type_t<E>> ToIntegral(E e) + constexpr inline std::enable_if_t<std::is_enum_v<E>, std::underlying_type_t<E>> ToIntegral(E e) { return static_cast<std::underlying_type_t<E>>(e); } // Get the enum value for an integral. template <typename E> - inline std::enable_if_t<std::is_enum_v<E>, E> ToEnum(std::underlying_type_t<E> ut) + constexpr inline std::enable_if_t<std::is_enum_v<E>, E> ToEnum(std::underlying_type_t<E> ut) { return static_cast<E>(ut); } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -21,6 +21,7 @@ namespace AppInstaller::Manifest enum class InstallerTypeEnum { + Unknown, Inno, Wix, Msi, @@ -30,14 +31,13 @@ namespace AppInstaller::Manifest Exe, Burn, MSStore, - Unknown, }; enum class UpdateBehaviorEnum { + Unknown, Install, UninstallPrevious, - Unknown, }; enum class InstallerSwitchType diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h @@ -9,6 +9,7 @@ #include <sddl.h> #include <Shlobj.h> #include <Shlwapi.h> +#include <wow64apiset.h> #include "TraceLogging.h" diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -190,6 +190,7 @@ <ClInclude Include="Microsoft\Schema\1_0\TagsTable.h" /> <ClInclude Include="Microsoft\Schema\1_0\VersionTable.h" /> <ClInclude Include="Microsoft\Schema\1_1\Interface.h" /> + <ClInclude Include="Microsoft\Schema\1_1\ManifestMetadataTable.h" /> <ClInclude Include="Microsoft\Schema\1_1\PackageFamilyNameTable.h" /> <ClInclude Include="Microsoft\Schema\1_1\ProductCodeTable.h" /> <ClInclude Include="Microsoft\Schema\1_1\SearchResultsTable.h" /> @@ -227,6 +228,7 @@ <ClCompile Include="Microsoft\Schema\1_0\PathPartTable.cpp" /> <ClCompile Include="Microsoft\Schema\1_0\SearchResultsTable_1_0.cpp" /> <ClCompile Include="Microsoft\Schema\1_1\Interface_1_1.cpp" /> + <ClCompile Include="Microsoft\Schema\1_1\ManifestMetadataTable.cpp" /> <ClCompile Include="Microsoft\Schema\1_1\SearchResultsTable_1_1.cpp" /> <ClCompile Include="Microsoft\Schema\MetadataTable.cpp" /> <ClCompile Include="Microsoft\Schema\Version.cpp" /> diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -132,6 +132,9 @@ <ClInclude Include="CompositeSource.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="Microsoft\Schema\1_1\ManifestMetadataTable.h"> + <Filter>Microsoft\Schema\1_1</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -197,6 +200,9 @@ <ClCompile Include="CompositeSource.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="Microsoft\Schema\1_1\ManifestMetadataTable.cpp"> + <Filter>Microsoft\Schema\1_1</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -145,7 +145,7 @@ namespace AppInstaller::Repository return {}; } - std::map<std::string, std::string> GetInstallationMetadata() const override + IPackageVersion::Metadata GetMetadata() const override { return {}; } diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp @@ -5,6 +5,7 @@ #include "Microsoft/PredefinedInstalledSourceFactory.h" #include "Microsoft/SQLiteIndex.h" #include "Microsoft/SQLiteIndexSource.h" +#include <winget/ManifestInstaller.h> using namespace std::string_literals; using namespace std::string_view_literals; @@ -72,7 +73,10 @@ namespace AppInstaller::Repository::Microsoft manifest.Installers[0].PackageFamilyName = familyName; // Use the family name as a unique key for the path - index.AddManifest(manifest, std::filesystem::path{ packageId.FamilyName().c_str() }); + auto manifestId = index.AddManifest(manifest, std::filesystem::path{ packageId.FamilyName().c_str() }); + + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, + Manifest::ManifestInstaller::InstallerTypeToString(Manifest::ManifestInstaller::InstallerTypeEnum::Msix)); } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp @@ -131,25 +131,27 @@ namespace AppInstaller::Repository::Microsoft } #endif - void SQLiteIndex::AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) + SQLiteIndex::IdType SQLiteIndex::AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) { AICLI_LOG(Repo, Info, << "Adding manifest from file [" << manifestPath << "]"); Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); - AddManifest(manifest, relativePath); + return AddManifest(manifest, relativePath); } - void SQLiteIndex::AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + SQLiteIndex::IdType SQLiteIndex::AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) { AICLI_LOG(Repo, Info, << "Adding manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath << "]"); SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_addmanifest"); - m_interface->AddManifest(m_dbconn, manifest, relativePath); + IdType result = m_interface->AddManifest(m_dbconn, manifest, relativePath); SetLastWriteTime(); savepoint.Commit(); + + return result; } bool SQLiteIndex::UpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) @@ -244,6 +246,16 @@ namespace AppInstaller::Repository::Microsoft return m_interface->GetVersionKeysById(m_dbconn, id); } + SQLiteIndex::MetadataResult SQLiteIndex::GetMetadataByManifestId(SQLite::rowid_t manifestId) const + { + return m_interface->GetMetadataByManifestId(m_dbconn, manifestId); + } + + void SQLiteIndex::SetMetadataByManifestId(IdType manifestId, PackageVersionMetadata metadata, std::string_view value) + { + m_interface->SetMetadataByManifestId(m_dbconn, manifestId, metadata, value); + } + // Recording last write time based on MSDN documentation stating that time returns a POSIX epoch time and thus // should be consistent across systems. void SQLiteIndex::SetLastWriteTime() diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h @@ -25,6 +25,12 @@ namespace AppInstaller::Repository::Microsoft // An id that refers to a specific application. using IdType = SQLite::rowid_t; + // The return type of Search + using SearchResult = Schema::ISQLiteIndex::SearchResult; + + // The return type of GetMetadataByManifestId + using MetadataResult = Schema::ISQLiteIndex::MetadataResult; + SQLiteIndex(const SQLiteIndex&) = delete; SQLiteIndex& operator=(const SQLiteIndex&) = delete; @@ -62,11 +68,13 @@ namespace AppInstaller::Repository::Microsoft // Adds the manifest at the repository relative path to the index. // If the function succeeds, the manifest has been added. - void AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); + // Returns the manifest id. + IdType AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); // Adds the manifest at the repository relative path to the index. // If the function succeeds, the manifest has been added. - void AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + // Returns the manifest id. + IdType AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); // Updates the manifest with matching { Id, Version, Channel } in the index. // The return value indicates whether the index was modified by the function. @@ -92,7 +100,7 @@ namespace AppInstaller::Repository::Microsoft bool CheckConsistency(bool log = false) const; // Performs a search based on the given criteria. - Schema::ISQLiteIndex::SearchResult Search(const SearchRequest& request) const; + SearchResult Search(const SearchRequest& request) const; // Gets the string for the given property and manifest id, if present. std::optional<std::string> GetPropertyByManifestId(IdType manifestId, PackageVersionProperty property) const; @@ -107,6 +115,12 @@ namespace AppInstaller::Repository::Microsoft // Gets all versions and channels for the given id. std::vector<Utility::VersionAndChannel> GetVersionKeysById(IdType id) const; + // Gets the string for the given metadata and manifest id, if present. + MetadataResult GetMetadataByManifestId(SQLite::rowid_t manifestId) const; + + // Sets the string for the given metadata and manifest id. + void SetMetadataByManifestId(IdType manifestId, PackageVersionMetadata metadata, std::string_view value); + private: // Constructor used to open an existing index. SQLiteIndex(const std::string& target, SQLite::Connection::OpenDisposition disposition, SQLite::Connection::OpenFlags flags); diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -72,9 +72,17 @@ namespace AppInstaller::Repository::Microsoft return GetManifestFromArgAndRelativePath(source->GetDetails().Arg, relativePathOpt.value()); } - std::map<std::string, std::string> GetInstallationMetadata() const override + IPackageVersion::Metadata GetMetadata() const override { - return {}; + auto metadata = GetSource()->GetIndex().GetMetadataByManifestId(m_manifestId); + + IPackageVersion::Metadata result; + for (auto&& data : metadata) + { + result.emplace(std::move(data)); + } + + return result; } private: diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.h @@ -27,6 +27,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 std::optional<SQLite::rowid_t> GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const override; std::vector<Utility::VersionAndChannel> GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const override; + // Version 1.1 + MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; + void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; + protected: // Creates the search results table. virtual std::unique_ptr<SearchResultsTable> CreateSearchResultsTable(const SQLite::Connection& connection) const; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp @@ -537,6 +537,15 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 return result; } + ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection&, SQLite::rowid_t) const + { + return {}; + } + + void Interface::SetMetadataByManifestId(SQLite::Connection&, SQLite::rowid_t, PackageVersionMetadata, std::string_view) + { + } + std::unique_ptr<SearchResultsTable> Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const { return std::make_unique<SearchResultsTable>(connection); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.h @@ -11,7 +11,7 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_0 { - // A table that represents a single manifest + // A table that represents the parts of a path struct PathPartTable { // The id type diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface.h @@ -21,6 +21,10 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_1 SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const override; std::vector<std::string> GetMultiPropertyByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMultiProperty property) const override; + // Version 1.1 + MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; + void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; + protected: std::unique_ptr<V1_0::SearchResultsTable> CreateSearchResultsTable(const SQLite::Connection& connection) const override; void PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const override; diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface_1_1.cpp @@ -20,6 +20,8 @@ #include "Microsoft/Schema/1_1/SearchResultsTable.h" +#include "Microsoft/Schema/1_1/ManifestMetadataTable.h" + namespace AppInstaller::Repository::Microsoft::Schema::V1_1 { @@ -136,6 +138,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_1 PackageFamilyNameTable::DeleteIfNotNeededByManifestId(connection, manifestId); ProductCodeTable::DeleteIfNotNeededByManifestId(connection, manifestId); + if (ManifestMetadataTable::Exists(connection)) + { + ManifestMetadataTable::DeleteByManifestId(connection, manifestId); + } + savepoint.Commit(); return manifestId; @@ -231,6 +238,32 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_1 } } + ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const + { + ISQLiteIndex::MetadataResult result; + + if (ManifestMetadataTable::Exists(connection)) + { + result = ManifestMetadataTable::GetMetadataByManifestId(connection, manifestId); + } + + return result; + } + + void Interface::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "setmetadatabymanifestid_v1_1"); + + if (!ManifestMetadataTable::Exists(connection)) + { + ManifestMetadataTable::Create(connection); + } + + ManifestMetadataTable::SetMetadataByManifestId(connection, manifestId, metadata, value); + + savepoint.Commit(); + } + std::unique_ptr<V1_0::SearchResultsTable> Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const { return std::make_unique<SearchResultsTable>(connection); diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestMetadataTable.h" +#include "SQLiteStatementBuilder.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + using namespace SQLite; + + static constexpr std::string_view s_ManifestMetadataTable_Table_Name = "manifest_metadata"sv; + static constexpr std::string_view s_ManifestMetadataTable_PrimaryKeyIndex_Name = "manifest_metadata_pk"sv; + static constexpr std::string_view s_ManifestMetadataTable_Manifest_Column = "manifest"sv; + static constexpr std::string_view s_ManifestMetadataTable_Metadata_Column = "metadata"sv; + static constexpr std::string_view s_ManifestMetadataTable_Value_Column = "value"sv; + + bool ManifestMetadataTable::Exists(const SQLite::Connection& connection) + { + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). + Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_ManifestMetadataTable_Table_Name); + + Statement statement = builder.Prepare(connection); + THROW_HR_IF(E_UNEXPECTED, !statement.Step()); + return statement.GetColumn<int64_t>(0) != 0; + } + + void ManifestMetadataTable::Create(SQLite::Connection& connection) + { + using namespace Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createmanifestmetadata_v1_1"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_ManifestMetadataTable_Table_Name).Columns({ + ColumnBuilder(s_ManifestMetadataTable_Manifest_Column, Type::Int64).NotNull(), + ColumnBuilder(s_ManifestMetadataTable_Metadata_Column, Type::Int64).NotNull(), + ColumnBuilder(s_ManifestMetadataTable_Value_Column, Type::Text) + }); + + createTableBuilder.Execute(connection); + + StatementBuilder createPKIndexBuilder; + createPKIndexBuilder.CreateUniqueIndex(s_ManifestMetadataTable_PrimaryKeyIndex_Name).On(s_ManifestMetadataTable_Table_Name). + Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column }); + createPKIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + ISQLiteIndex::MetadataResult ManifestMetadataTable::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + using namespace Builder; + + StatementBuilder builder; + builder.Select({ s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }).From(s_ManifestMetadataTable_Table_Name). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); + + Statement statement = builder.Prepare(connection); + + ISQLiteIndex::MetadataResult result; + while (statement.Step()) + { + result.emplace_back(std::make_pair(statement.GetColumn<PackageVersionMetadata>(0), statement.GetColumn<std::string>(1))); + } + + return result; + } + + void ManifestMetadataTable::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) + { + using namespace Builder; + + // First, we attempt to update an existing row. If not changes occurred, we then insert the new value. + // UPSERT (aka ON CONFLICT) is not available to us, as it was only introduced in 3.24.0 (2018-06-04), + // and we need to support Windows 10 (16299) which was released in 2017. + StatementBuilder updateBuilder; + updateBuilder.Update(s_ManifestMetadataTable_Table_Name).Set().Column(s_ManifestMetadataTable_Value_Column).Equals(value). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId).And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); + + updateBuilder.Execute(connection); + + // No changes means we need to insert the row + if (connection.GetChanges() == 0) + { + StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_ManifestMetadataTable_Table_Name). + Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }) + .Values(manifestId, metadata, value); + + insertBuilder.Execute(connection); + } + } + + void ManifestMetadataTable::DeleteByManifestId(SQLite::Connection & connection, SQLite::rowid_t manifestId) + { + using namespace Builder; + + StatementBuilder builder; + builder.DeleteFrom(s_ManifestMetadataTable_Table_Name).Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); + builder.Execute(connection); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SQLiteWrapper.h" +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "AppInstallerRepositorySearch.h" + +#include <string> +#include <string_view> +#include <vector> + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + // A table for storing arbitrary metadata on idividual manifests. + // The table and all metadata are optional. + struct ManifestMetadataTable + { + // Determine if the table currently exists in the database. + static bool Exists(const SQLite::Connection& connection); + + // Creates the table in the database. + static void Create(SQLite::Connection& connection); + + // Gets all metadata associated with the given manifest. + // The table must exist. + static ISQLiteIndex::MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId); + + // Sets the metadata value for the given manifest. + // The table must exist. + static void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value); + + // Removes all metadata values for the given manifest. + // The table must exist. + static void DeleteByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h @@ -28,6 +28,9 @@ namespace AppInstaller::Repository::Microsoft::Schema bool Truncated = false; }; + // The non-version specific return value of GetMetadataByManifestId. + using MetadataResult = std::vector<std::pair<PackageVersionMetadata, std::string>>; + // Version 1.0 // Gets the schema version that this index interface is built for. @@ -69,5 +72,13 @@ namespace AppInstaller::Repository::Microsoft::Schema // Gets all versions and channels for the given id. virtual std::vector<Utility::VersionAndChannel> GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const = 0; + + // Version 1.1 + + // Gets the string for the given metadata and manifest id, if present. + virtual MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const = 0; + + // Sets the string for the given metadata and manifest id. + virtual void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) = 0; }; } diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h @@ -107,9 +107,21 @@ namespace AppInstaller::Repository ProductCode, }; + // A metadata item of a package version. + enum class PackageVersionMetadata : int32_t + { + // The InstallerType of an installed package + InstalledType, + }; + + // Convert a PackageVersionMetadata to a string. + std::string_view ToString(PackageVersionMetadata pvm); + // A single package version. struct IPackageVersion { + using Metadata = std::map<PackageVersionMetadata, std::string>; + virtual ~IPackageVersion() = default; // Gets a property of this package version. @@ -121,15 +133,16 @@ namespace AppInstaller::Repository // Gets the manifest of this package version. virtual Manifest::Manifest GetManifest() const = 0; - // Gets any metadata associated with this version if it is installed. - virtual std::map<std::string, std::string> GetInstallationMetadata() const = 0; + // Gets any metadata associated with this package version. + // Primarily stores data on installed packages. + virtual Metadata GetMetadata() const = 0; }; // An installed package version. struct IInstalledPackageVersion : public IPackageVersion { // Sets metadata on the installed version. - virtual void SetInstallationMetadata(std::string_view key, std::string_view value) = 0; + virtual void SetMetadata(PackageVersionMetadata metadata, std::string_view value) = 0; }; // A key to identify a package version within a package. diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -772,6 +772,15 @@ namespace AppInstaller::Repository return result.str(); } + std::string_view ToString(PackageVersionMetadata pvm) + { + switch (pvm) + { + case PackageVersionMetadata::InstalledType: return "InstalledType"sv; + default: return "Unknown"sv; + } + } + #ifndef AICLI_DISABLE_TEST_HOOKS void TestHook_SetSourceFactoryOverride(const std::string& type, std::function<std::unique_ptr<ISourceFactory>()>&& factory) { diff --git a/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h b/src/AppInstallerRepositoryCore/SQLiteStatementBuilder.h @@ -12,6 +12,8 @@ #include <string_view> #include <vector> +using namespace std::string_view_literals; + namespace AppInstaller::Repository::SQLite::Builder { namespace details @@ -67,10 +69,26 @@ namespace AppInstaller::Repository::SQLite::Builder std::string_view Schema; std::string_view Table; - explicit QualifiedTable(std::string_view table) : Table(table) {} - explicit QualifiedTable(std::string_view schema, std::string_view table) : Schema(schema), Table(table) {} + explicit constexpr QualifiedTable(std::string_view table) : Table(table) {} + explicit constexpr QualifiedTable(std::string_view schema, std::string_view table) : Schema(schema), Table(table) {} }; + namespace Schema + { + // The main database's schema table. + // More info can be found at: https://www.sqlite.org/schematab.html + constexpr QualifiedTable MainTable{ "main"sv, "sqlite_master"sv }; + + // The sqlite_schema column name for the type of the object. + constexpr std::string_view TypeColumn = "type"sv; + + // The sqlite_schema type value for a table. + constexpr std::string_view Type_Table = "table"sv; + + // The sqlite_schema column name for the name of the object. + constexpr std::string_view NameColumn = "name"sv; + } + // A qualified column reference. struct QualifiedColumn {