winget-cli

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

commit b7b2f0e8583e3c522989421b6d439bb1048045b1
parent 3b3a1083c23e8ae62ca1d0c7702e974426a888dc
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date:   Tue, 30 May 2023 14:23:38 -0700

Add icons entries to yaml manifest, rest interface and com interface (#3273)


Diffstat:
Mschemas/JSON/manifests/v1.5.0/manifest.defaultLocale.1.5.0.json | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mschemas/JSON/manifests/v1.5.0/manifest.locale.1.5.0.json | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mschemas/JSON/manifests/v1.5.0/manifest.singleton.1.5.0.json | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs | 25+++++++++++++++++++++++--
Msrc/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_CatalogPackageMetadata.yaml | 12+++++++++---
Msrc/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_MultipleLocale.yaml | 12+++++++++---
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 1+
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 3+++
Msrc/AppInstallerCLITests/RestClient.cpp | 2+-
Msrc/AppInstallerCLITests/RestInterface_1_0.cpp | 1-
Msrc/AppInstallerCLITests/RestInterface_1_1.cpp | 1-
Msrc/AppInstallerCLITests/RestInterface_1_4.cpp | 1-
Asrc/AppInstallerCLITests/RestInterface_1_5.cpp | 402+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml | 8+++++++-
Msrc/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-DefaultLocale.yaml | 6++++++
Msrc/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-Locale.yaml | 8+++++++-
Msrc/AppInstallerCLITests/YamlManifest.cpp | 20++++++++++++++++++++
Msrc/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp | 56+++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/AppInstallerCommonCore/Public/winget/ManifestLocalization.h | 16++++++++++++++++
Msrc/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h | 4++++
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj | 4++++
Msrc/AppInstallerRepositoryCore/InstallerMetadataCollectionContext.cpp | 8++++----
Msrc/AppInstallerRepositoryCore/ManifestJSONParser.cpp | 7++++++-
Msrc/AppInstallerRepositoryCore/Rest/RestClient.cpp | 7++++++-
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h | 3---
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp | 4----
Asrc/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h | 21+++++++++++++++++++++
Asrc/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer.h | 13+++++++++++++
Asrc/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer_1_5.cpp | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp | 25+++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h | 1+
Msrc/Microsoft.Management.Deployment/CatalogPackageMetadata.cpp | 16++++++++++++++++
Msrc/Microsoft.Management.Deployment/CatalogPackageMetadata.h | 4++++
Msrc/Microsoft.Management.Deployment/Converters.cpp | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/Microsoft.Management.Deployment/Converters.h | 3+++
Msrc/Microsoft.Management.Deployment/Documentation.cpp | 1-
Asrc/Microsoft.Management.Deployment/Icon.cpp | 34++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Deployment/Icon.h | 28++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Deployment/InstalledStatus.cpp | 1-
Msrc/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj | 2++
Msrc/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters | 2++
Msrc/Microsoft.Management.Deployment/PackageManager.idl | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
42 files changed, 1137 insertions(+), 35 deletions(-)

diff --git a/schemas/JSON/manifests/v1.5.0/manifest.defaultLocale.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.defaultLocale.1.5.0.json @@ -50,6 +50,64 @@ "description": "The documentation URL." } } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "$ref": "#/definitions/Url", + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] } }, "type": "object", @@ -186,6 +244,13 @@ }, "maxItems": 256 }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, "ManifestType": { "type": "string", "default": "defaultLocale", diff --git a/schemas/JSON/manifests/v1.5.0/manifest.locale.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.locale.1.5.0.json @@ -50,6 +50,64 @@ "description": "The documentation URL." } } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "$ref": "#/definitions/Url", + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] } }, "type": "object", @@ -181,6 +239,13 @@ }, "maxItems": 256 }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, "ManifestType": { "type": "string", "default": "locale", diff --git a/schemas/JSON/manifests/v1.5.0/manifest.singleton.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.singleton.1.5.0.json @@ -69,6 +69,64 @@ } } }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "$ref": "#/definitions/Url", + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + }, "Channel": { "type": [ "string", "null" ], "minLength": 1, @@ -864,6 +922,13 @@ }, "maxItems": 256 }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, "Channel": { "$ref": "#/definitions/Channel" }, diff --git a/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs b/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs @@ -6,10 +6,11 @@ namespace AppInstallerCLIE2ETests.Interop { + using System; using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; - + using NUnit.Framework; + /// <summary> /// Test find package interop. /// </summary> @@ -117,6 +118,16 @@ namespace AppInstallerCLIE2ETests.Interop var documentation = documentations[0]; Assert.AreEqual("testDocumentLabel", documentation.DocumentLabel); Assert.AreEqual("https://testDocumentUrl.com", documentation.DocumentUrl); + + var icons = catalogPackageMetadata.Icons; + Assert.AreEqual(1, icons.Count); + + var icon = icons[0]; + Assert.AreEqual("https://testIcon", icon.Url); + Assert.AreEqual(IconFileType.Ico, icon.FileType); + Assert.AreEqual(IconTheme.Default, icon.Theme); + Assert.AreEqual(IconResolution.Custom, icon.Resolution); + Assert.AreEqual(Convert.FromHexString("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123"), icon.Sha256); } /// <summary> @@ -173,6 +184,16 @@ namespace AppInstallerCLIE2ETests.Interop var documentation = documentations[0]; Assert.AreEqual("localeDocumentLabel", documentation.DocumentLabel); Assert.AreEqual("https://localeDocumentUrl.com", documentation.DocumentUrl); + + var icons = catalogPackageMetadata.Icons; + Assert.AreEqual(1, icons.Count); + + var icon = icons[0]; + Assert.AreEqual("https://localeTestIcon", icon.Url); + Assert.AreEqual(IconFileType.Png, icon.FileType); + Assert.AreEqual(IconTheme.Light, icon.Theme); + Assert.AreEqual(IconResolution.Square32, icon.Resolution); + Assert.AreEqual(Convert.FromHexString("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321"), icon.Sha256); } /// <summary> diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_CatalogPackageMetadata.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_CatalogPackageMetadata.yaml @@ -42,6 +42,12 @@ ReleaseNotesUrl: https://testReleaseNotes.net InstallationNotes: testInstallationNotes Documentations: - DocumentLabel: testDocumentLabel - DocumentUrl: https://testDocumentUrl.com + DocumentUrl: https://testDocumentUrl.com +Icons: + - IconUrl: https://testIcon + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 ManifestType: singleton -ManifestVersion: 1.2.0- \ No newline at end of file +ManifestVersion: 1.5.0+ \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_MultipleLocale.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_MultipleLocale.yaml @@ -45,7 +45,13 @@ Localization: AgreementUrl: https://localeAgreementUrl.net Documentations: - DocumentLabel: localeDocumentLabel - DocumentUrl: https://localeDocumentUrl.com + DocumentUrl: https://localeDocumentUrl.com + Icons: + - IconUrl: https://localeTestIcon + IconFileType: png + IconResolution: 32x32 + IconTheme: light + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 Tags: - tag1 - tag2 @@ -54,4 +60,4 @@ Localization: License: licenseUK Publisher: publisherUK ManifestType: merged -ManifestVersion: 1.2.0- \ No newline at end of file +ManifestVersion: 1.5.0+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -237,6 +237,7 @@ <ClCompile Include="RestInterface_1_0.cpp" /> <ClCompile Include="RestInterface_1_1.cpp" /> <ClCompile Include="RestInterface_1_4.cpp" /> + <ClCompile Include="RestInterface_1_5.cpp" /> <ClCompile Include="Runtime.cpp" /> <ClCompile Include="SearchRequestSerializer.cpp" /> <ClCompile Include="ShowFlow.cpp" /> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -296,6 +296,9 @@ <ClCompile Include="RestInterface_1_4.cpp"> <Filter>Source Files\Repository</Filter> </ClCompile> + <ClCompile Include="RestInterface_1_5.cpp"> + <Filter>Source Files\Repository</Filter> + </ClCompile> <ClCompile Include="IconExtraction.cpp"> <Filter>Source Files\Repository</Filter> </ClCompile> diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp @@ -50,7 +50,7 @@ TEST_CASE("GetSupportedInterface", "[RestSource]") REQUIRE(RestClient::GetSupportedInterface(utility::conversions::to_utf8string(TestRestUri), {}, info, version)->GetVersion() == version); // Update this test to next version so that we don't forget to add to supported versions before rest e2e tests are available. - Version invalid{ "1.5.0" }; + Version invalid{ "1.6.0" }; REQUIRE_THROWS(RestClient::GetSupportedInterface(utility::conversions::to_utf8string(TestRestUri), {}, info, invalid)); } diff --git a/src/AppInstallerCLITests/RestInterface_1_0.cpp b/src/AppInstallerCLITests/RestInterface_1_0.cpp @@ -56,7 +56,6 @@ namespace { utility::string_t GetSampleManifest_AllFields() { - utility::string_t id = L"Foo.Bar"; return _XPLATSTR( R"delimiter( { diff --git a/src/AppInstallerCLITests/RestInterface_1_1.cpp b/src/AppInstallerCLITests/RestInterface_1_1.cpp @@ -36,7 +36,6 @@ namespace { utility::string_t GetSampleManifest_AllFields() { - utility::string_t id = L"Foo.Bar"; return _XPLATSTR( R"delimiter( { diff --git a/src/AppInstallerCLITests/RestInterface_1_4.cpp b/src/AppInstallerCLITests/RestInterface_1_4.cpp @@ -36,7 +36,6 @@ namespace { utility::string_t GetSampleManifest_AllFields() { - utility::string_t id = L"Foo.Bar"; return _XPLATSTR( R"delimiter( { diff --git a/src/AppInstallerCLITests/RestInterface_1_5.cpp b/src/AppInstallerCLITests/RestInterface_1_5.cpp @@ -0,0 +1,402 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include <Rest/Schema/1_5/Interface.h> +#include <Rest/Schema/IRestClient.h> +#include <AppInstallerVersions.h> +#include <AppInstallerErrors.h> + +using namespace TestCommon; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_5; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + IRestClient::Information GetTestSourceInformation() + { + IRestClient::Information result; + + result.RequiredPackageMatchFields.emplace_back("Market"); + result.RequiredQueryParameters.emplace_back("Market"); + result.UnsupportedPackageMatchFields.emplace_back("Moniker"); + result.UnsupportedQueryParameters.emplace_back("Channel"); + + return result; + } + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "install", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + } + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Publisher>() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherUrl>() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Author>() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageName>() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageUrl>() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::License>() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Description>() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotes>() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotesUrl>() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PurchaseUrl>() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::InstallationNotes>() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get<Localization::Publisher>() == "Foo French"); + REQUIRE(frenchLocalization.Get<Localization::PublisherUrl>() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::Author>() == "FooBar French"); + REQUIRE(frenchLocalization.Get<Localization::PackageName>() == "Bar"); + REQUIRE(frenchLocalization.Get<Localization::PackageUrl>() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::License>() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get<Localization::Description>() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get<Localization::Tags>().size() == 3); + REQUIRE(frenchLocalization.Get<Localization::Tags>().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get<Localization::Tags>().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get<Localization::Tags>().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get<Localization::ReleaseNotes>() == "Release notes"); + REQUIRE(frenchLocalization.Get<Localization::ReleaseNotesUrl>() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().size() == 1); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get<Localization::PurchaseUrl>() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get<Localization::InstallationNotes>() == "Installation Notes"); + REQUIRE(frenchLocalization.Get<Localization::Documentations>().size() == 1); + REQUIRE(frenchLocalization.Get<Localization::Documentations>().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get<Localization::Icons>().size() == 1); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 7); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_5", "[RestSource][Interface_1_5]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_5{ TestRestUriString, {}, {}, std::move(helper) }; + std::vector<Manifest> manifests = v1_5.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml @@ -24,7 +24,13 @@ PurchaseUrl: https://DefaultPurchaseUrl.com InstallationNotes: Default installation notes Documentations: - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 Agreements: - AgreementLabel: DefaultLabel Agreement: DefaultText diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-DefaultLocale.yaml @@ -25,6 +25,12 @@ ReleaseNotesUrl: https://DefaultReleaseNotes.net Documentations: - DocumentLabel: Default document label DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 Agreements: - AgreementLabel: DefaultLabel Agreement: DefaultText diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_5/ManifestV1_5-MultiFile-Locale.yaml @@ -27,6 +27,12 @@ Agreements: AgreementUrl: https://AgreementUrl.net Documentations: - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://localeTestIcon + IconFileType: png + IconResolution: 32x32 + IconTheme: light + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 ManifestType: locale ManifestVersion: 1.5.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -433,6 +433,16 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); } + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) + { + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Url == "https://testIcon"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + } + REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector<PlatformEnum>{ PlatformEnum::Desktop, PlatformEnum::Universal }); REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); @@ -714,6 +724,16 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, Manifes REQUIRE(localization1.Get<Localization::Documentations>().at(0).DocumentLabel == "Default document label"); REQUIRE(localization1.Get<Localization::Documentations>().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) + { + REQUIRE(localization1.Get<Localization::Icons>().size() == 1); + REQUIRE(localization1.Get<Localization::Icons>().at(0).Url == "https://localeTestIcon"); + REQUIRE(localization1.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(localization1.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(localization1.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Light); + REQUIRE(localization1.Get<Localization::Icons>().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } } } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -454,6 +454,16 @@ namespace AppInstaller::Manifest std::move(fields_v1_2.begin(), fields_v1_2.end(), std::inserter(result, result.end())); } + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_5 }) + { + std::vector<FieldProcessInfo> fields_v1_5 = + { + { "Icons", [this](const YAML::Node& value)->ValidationErrors { return ProcessIconsNode(value); }, true }, + }; + + std::move(fields_v1_5.begin(), fields_v1_5.end(), std::inserter(result, result.end())); + } } return result; @@ -571,6 +581,25 @@ namespace AppInstaller::Manifest return result; } + std::vector<ManifestYamlPopulator::FieldProcessInfo> ManifestYamlPopulator::GetIconFieldProcessInfo(const ManifestVer& manifestVersion) + { + std::vector<FieldProcessInfo> result = {}; + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_5 }) + { + result = + { + { "IconUrl", [this](const YAML::Node& value)->ValidationErrors { m_p_icon->Url = Utility::Trim(value.as<std::string>()); return {}; } }, + { "IconFileType", [this](const YAML::Node& value)->ValidationErrors { m_p_icon->FileType = ConvertToIconFileTypeEnum(value.as<std::string>()); return {}; } }, + { "IconResolution", [this](const YAML::Node& value)->ValidationErrors { m_p_icon->Resolution = ConvertToIconResolutionEnum(value.as<std::string>()); return {}; } }, + { "IconTheme", [this](const YAML::Node& value)->ValidationErrors { m_p_icon->Theme = ConvertToIconThemeEnum(value.as<std::string>()); return {}; } }, + { "IconSha256", [this](const YAML::Node& value)->ValidationErrors { m_p_icon->Sha256 = Utility::SHA256::ConvertToBytes(value.as<std::string>()); return {}; } }, + }; + } + + return result; + } + std::vector<ManifestYamlPopulator::FieldProcessInfo> ManifestYamlPopulator::GetNestedInstallerFileFieldProcessInfo(const ManifestVer& manifestVersion) { std::vector<FieldProcessInfo> result = {}; @@ -795,7 +824,7 @@ namespace AppInstaller::Manifest ValidationErrors ManifestYamlPopulator::ProcessDocumentationsNode(const YAML::Node& documentationsNode) { THROW_HR_IF(E_INVALIDARG, !documentationsNode.IsSequence()); - + ValidationErrors resultErrors; std::vector<Documentation> documentations; @@ -816,6 +845,30 @@ namespace AppInstaller::Manifest return resultErrors; } + std::vector<ValidationError> ManifestYamlPopulator::ProcessIconsNode(const YAML::Node& iconsNode) + { + THROW_HR_IF(E_INVALIDARG, !iconsNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector<Icon> icons; + + for (auto const& entry : iconsNode.Sequence()) + { + Icon icon; + m_p_icon = &icon; + auto errors = ValidateAndProcessFields(entry, IconFieldInfos); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + icons.emplace_back(std::move(icon)); + } + + if (!icons.empty()) + { + m_p_localization->Add<Localization::Icons>(std::move(icons)); + } + + return resultErrors; + } + ValidationErrors ManifestYamlPopulator::ProcessNestedInstallerFilesNode(const YAML::Node& nestedInstallerFilesNode) { THROW_HR_IF(E_INVALIDARG, !nestedInstallerFilesNode.IsSequence()); @@ -888,6 +941,7 @@ namespace AppInstaller::Manifest MarketsFieldInfos = GetMarketsFieldProcessInfo(manifestVersion); AppsAndFeaturesEntryFieldInfos = GetAppsAndFeaturesEntryFieldProcessInfo(manifestVersion); DocumentationFieldInfos = GetDocumentationFieldProcessInfo(manifestVersion); + IconFieldInfos = GetIconFieldProcessInfo(manifestVersion); NestedInstallerFileFieldInfos = GetNestedInstallerFileFieldProcessInfo(manifestVersion); InstallationMetadataFieldInfos = GetInstallationMetadataFieldProcessInfo(manifestVersion); InstallationMetadataFilesFieldInfos = GetInstallationMetadataFilesFieldProcessInfo(manifestVersion); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -31,6 +31,7 @@ namespace AppInstaller::Manifest ReleaseNotesUrl, PurchaseUrl, InstallationNotes, + Icons, Max }; @@ -47,6 +48,15 @@ namespace AppInstaller::Manifest string_t DocumentUrl; }; + struct Icon + { + string_t Url; + IconFileTypeEnum FileType = IconFileTypeEnum::Unknown; + IconResolutionEnum Resolution = IconResolutionEnum::Unknown; + IconThemeEnum Theme = IconThemeEnum::Unknown; + std::vector<BYTE> Sha256; + }; + namespace details { template <Localization L> @@ -73,6 +83,12 @@ namespace AppInstaller::Manifest using value_t = std::vector<Documentation>; }; + template <> + struct LocalizationMapping<Localization::Icons> + { + using value_t = std::vector<Icon>; + }; + // Used to deduce the LocalizationVariant type; making a variant that includes std::monostate and all LocalizationMapping types. template <size_t... I> inline auto Deduce(std::index_sequence<I...>) { return std::variant<std::monostate, typename LocalizationMapping<static_cast<Localization>(I)>::value_t...>{}; } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -42,6 +42,7 @@ namespace AppInstaller::Manifest std::vector<FieldProcessInfo> MarketsFieldInfos; std::vector<FieldProcessInfo> AppsAndFeaturesEntryFieldInfos; std::vector<FieldProcessInfo> DocumentationFieldInfos; + std::vector<FieldProcessInfo> IconFieldInfos; std::vector<FieldProcessInfo> NestedInstallerFileFieldInfos; std::vector<FieldProcessInfo> InstallationMetadataFieldInfos; std::vector<FieldProcessInfo> InstallationMetadataFilesFieldInfos; @@ -58,6 +59,7 @@ namespace AppInstaller::Manifest AppInstaller::Manifest::MarketsInfo* m_p_markets = nullptr; AppInstaller::Manifest::AppsAndFeaturesEntry* m_p_appsAndFeaturesEntry = nullptr; AppInstaller::Manifest::Documentation* m_p_documentation = nullptr; + AppInstaller::Manifest::Icon* m_p_icon = nullptr; AppInstaller::Manifest::NestedInstallerFile* m_p_nestedInstallerFile = nullptr; AppInstaller::Manifest::InstallationMetadataInfo* m_p_installationMetadata = nullptr; AppInstaller::Manifest::InstalledFile* m_p_installedFile = nullptr; @@ -77,6 +79,7 @@ namespace AppInstaller::Manifest std::vector<FieldProcessInfo> GetMarketsFieldProcessInfo(const ManifestVer& manifestVersion); std::vector<FieldProcessInfo> GetAppsAndFeaturesEntryFieldProcessInfo(const ManifestVer& manifestVersion); std::vector<FieldProcessInfo> GetDocumentationFieldProcessInfo(const ManifestVer& manifestVersion); + std::vector<FieldProcessInfo> GetIconFieldProcessInfo(const ManifestVer& manifestVersion); std::vector<FieldProcessInfo> GetNestedInstallerFileFieldProcessInfo(const ManifestVer& manifestVersion); std::vector<FieldProcessInfo> GetInstallationMetadataFieldProcessInfo(const ManifestVer& manifestVersion); std::vector<FieldProcessInfo> GetInstallationMetadataFilesFieldProcessInfo(const ManifestVer& manifestVersion); @@ -96,6 +99,7 @@ namespace AppInstaller::Manifest std::vector<ValidationError> ProcessAppsAndFeaturesEntriesNode(const YAML::Node& appsAndFeaturesEntriesNode); std::vector<ValidationError> ProcessExpectedReturnCodesNode(const YAML::Node& returnCodesNode); std::vector<ValidationError> ProcessDocumentationsNode(const YAML::Node& documentationsNode); + std::vector<ValidationError> ProcessIconsNode(const YAML::Node& iconsNode); std::vector<ValidationError> ProcessNestedInstallerFilesNode(const YAML::Node& nestedInstallerFilesNode); std::vector<ValidationError> ProcessInstallationMetadataFilesNode(const YAML::Node& installedFilesNode); diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -411,6 +411,8 @@ <ClInclude Include="Rest\Schema\1_4\Interface.h" /> <ClInclude Include="Rest\Schema\1_4\Json\ManifestDeserializer.h" /> <ClInclude Include="Rest\Schema\1_4\Json\SearchResponseDeserializer.h" /> + <ClInclude Include="Rest\Schema\1_5\Interface.h" /> + <ClInclude Include="Rest\Schema\1_5\Json\ManifestDeserializer.h" /> <ClInclude Include="Rest\Schema\CommonRestConstants.h" /> <ClInclude Include="Rest\Schema\HttpClientHelper.h" /> <ClInclude Include="Rest\Schema\InformationResponseDeserializer.h" /> @@ -502,6 +504,8 @@ <ClCompile Include="Rest\Schema\1_4\Json\ManifestDeserializer_1_4.cpp" /> <ClCompile Include="Rest\Schema\1_4\Json\SearchResponseDeserializer_1_4.cpp" /> <ClCompile Include="Rest\Schema\1_4\RestInterface_1_4.cpp" /> + <ClCompile Include="Rest\Schema\1_5\Json\ManifestDeserializer_1_5.cpp" /> + <ClCompile Include="Rest\Schema\1_5\RestInterface_1_5.cpp" /> <ClCompile Include="Rest\Schema\HttpClientHelper.cpp" /> <ClCompile Include="Rest\Schema\InformationResponseDeserializer.cpp" /> <ClCompile Include="Rest\Schema\RestHelper.cpp" /> diff --git a/src/AppInstallerRepositoryCore/InstallerMetadataCollectionContext.cpp b/src/AppInstallerRepositoryCore/InstallerMetadataCollectionContext.cpp @@ -550,8 +550,8 @@ namespace AppInstaller::Repository::Metadata ProductVersionMax = Version{ std::move(productVersionMaxString).value() }; } - // The 1.0 version of metadata uses the 1.4 version of REST - JSON::ManifestJSONParser parser{ Version{ "1.4" } }; + // The 1.0 version of metadata uses the 1.5 version of REST + JSON::ManifestJSONParser parser{ Version{ "1.5" } }; std::string submissionIdentifierVerification; @@ -1274,8 +1274,8 @@ namespace AppInstaller::Repository::Metadata m_installerHash = GetRequiredString(packageDataValue.value(), installerHashFieldName); - // The 1.0 version of input uses the 1.4 version of REST - JSON::ManifestJSONParser parser{ Version{ "1.4" }}; + // The 1.0 version of input uses the 1.5 version of REST + JSON::ManifestJSONParser parser{ Version{ "1.5" }}; { auto defaultLocaleValue = AppInstaller::JSON::GetJsonValueFromNode(packageDataValue.value(), defaultLocaleFieldName); diff --git a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp @@ -5,6 +5,7 @@ #include "Rest/Schema/1_0/Json/ManifestDeserializer.h" #include "Rest/Schema/1_1/Json/ManifestDeserializer.h" #include "Rest/Schema/1_4/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_5/Json/ManifestDeserializer.h" namespace AppInstaller::Repository::JSON { @@ -31,10 +32,14 @@ namespace AppInstaller::Repository::JSON { m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_1::Json::ManifestDeserializer>(); } - else + else if (parts.size() > 1 && parts[1].Integer < 5) { m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_4::Json::ManifestDeserializer>(); } + else + { + m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_5::Json::ManifestDeserializer>(); + } } else { diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -5,6 +5,7 @@ #include "Rest/Schema/1_0/Interface.h" #include "Rest/Schema/1_1/Interface.h" #include "Rest/Schema/1_4/Interface.h" +#include "Rest/Schema/1_5/Interface.h" #include "Rest/Schema/HttpClientHelper.h" #include <winget/JsonUtil.h> #include "Rest/Schema/InformationResponseDeserializer.h" @@ -18,7 +19,7 @@ using namespace AppInstaller::Utility; namespace AppInstaller::Repository::Rest { // Supported versions - std::set<Version> WingetSupportedContracts = { Version_1_0_0, Version_1_1_0, Version_1_4_0 }; + std::set<Version> WingetSupportedContracts = { Version_1_0_0, Version_1_1_0, Version_1_4_0, Version_1_5_0 }; constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv; constexpr size_t WindowsPackageManagerHeaderMaxLength = 1024; @@ -147,6 +148,10 @@ namespace AppInstaller::Repository::Rest { return std::make_unique<Schema::V1_4::Interface>(api, information, additionalHeaders); } + else if (version == Version_1_5_0) + { + return std::make_unique<Schema::V1_5::Interface>(api, information, additionalHeaders); + } THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h @@ -17,8 +17,5 @@ namespace AppInstaller::Repository::Rest::Schema::V1_4 Interface& operator=(Interface&&) = default; Utility::Version GetVersion() const override; - - private: - IRestClient::Information m_information; }; } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp @@ -5,12 +5,8 @@ #include "Rest/Schema/IRestClient.h" #include "Rest/Schema/HttpClientHelper.h" #include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/1_4/Json/ManifestDeserializer.h" #include <winget/JsonUtil.h> -using namespace std::string_view_literals; -using namespace AppInstaller::Repository::Rest::Schema::V1_4::Json; - namespace AppInstaller::Repository::Rest::Schema::V1_4 { Interface::Interface( diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_4/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_5 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_4::Interface + { + Interface(const std::string& restApi, IRestClient::Information information, const std::unordered_map<utility::string_t, utility::string_t>& additionalHeaders = {}, const HttpClientHelper& httpClientHelper = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer.h @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_4/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_5::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_4::Json::ManifestDeserializer + { + std::optional<Manifest::ManifestLocalization> DeserializeLocale(const web::json::value& localeJsonObject) const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer_1_5.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer_1_5.cpp @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include <winget/JsonUtil.h> + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_5::Json +{ + namespace + { + // Locale + constexpr std::string_view Icons = "Icons"sv; + constexpr std::string_view IconUrl = "IconUrl"sv; + constexpr std::string_view IconFileType = "IconFileType"sv; + constexpr std::string_view IconResolution = "IconResolution"sv; + constexpr std::string_view IconTheme = "IconTheme"sv; + constexpr std::string_view IconSha256 = "IconSha256"sv; + } + + std::optional<Manifest::ManifestLocalization> ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const + { + auto result = V1_4::Json::ManifestDeserializer::DeserializeLocale(localeJsonObject); + + if (result) + { + auto& locale = result.value(); + + // Icons + auto iconsNode = JSON::GetRawJsonArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Icons)); + if (iconsNode) + { + std::vector<Manifest::Icon> icons; + for (auto const& iconNode : iconsNode->get()) + { + Manifest::Icon iconEntry; + + iconEntry.Url = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconUrl)).value_or(""); + if (iconEntry.Url.empty()) + { + continue; + } + + auto fileType = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconFileType)).value_or(""); + if (fileType.empty()) + { + continue; + } + + iconEntry.FileType = Manifest::ConvertToIconFileTypeEnum(fileType); + iconEntry.Resolution = Manifest::ConvertToIconResolutionEnum(JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconResolution)).value_or("")); + iconEntry.Theme = Manifest::ConvertToIconThemeEnum(JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconTheme)).value_or("")); + + std::optional<std::string> sha256 = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconSha256)); + if (JSON::IsValidNonEmptyStringValue(sha256)) + { + iconEntry.Sha256 = Utility::SHA256::ConvertToBytes(*sha256); + } + + icons.emplace_back(std::move(iconEntry)); + } + + if (!icons.empty()) + { + locale.Add<Manifest::Localization::Icons>(std::move(icons)); + } + } + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_5/Interface.h" +#include "Rest/Schema/IRestClient.h" +#include "Rest/Schema/HttpClientHelper.h" +#include "Rest/Schema/CommonRestConstants.h" +#include <winget/JsonUtil.h> + +namespace AppInstaller::Repository::Rest::Schema::V1_5 +{ + Interface::Interface( + const std::string& restApi, + IRestClient::Information information, + const std::unordered_map<utility::string_t, utility::string_t>& additionalHeaders, + const HttpClientHelper& httpClientHelper) : V1_4::Interface(restApi, std::move(information), additionalHeaders, httpClientHelper) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_5_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_5_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h @@ -9,6 +9,7 @@ namespace AppInstaller::Repository::Rest::Schema const Utility::Version Version_1_0_0{ "1.0.0" }; const Utility::Version Version_1_1_0{ "1.1.0" }; const Utility::Version Version_1_4_0{ "1.4.0" }; + const Utility::Version Version_1_5_0{ "1.5.0" }; // General API response constants constexpr std::string_view Data = "Data"sv; diff --git a/src/Microsoft.Management.Deployment/CatalogPackageMetadata.cpp b/src/Microsoft.Management.Deployment/CatalogPackageMetadata.cpp @@ -134,4 +134,20 @@ namespace winrt::Microsoft::Management::Deployment::implementation return m_documentations.GetView(); } + winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Management::Deployment::Icon> CatalogPackageMetadata::Icons() + { + if (!m_icons) + { + auto icons = winrt::single_threaded_vector<winrt::Microsoft::Management::Deployment::Icon>(); + for (auto const& icon : m_manifestLocalization.Get<AppInstaller::Manifest::Localization::Icons>()) + { + auto iconImpl = winrt::make_self<wil::details::module_count_wrapper<winrt::Microsoft::Management::Deployment::implementation::Icon>>(); + iconImpl->Initialize(icon); + icons.Append(*iconImpl); + } + m_icons = icons; + } + + return m_icons.GetView(); + } } diff --git a/src/Microsoft.Management.Deployment/CatalogPackageMetadata.h b/src/Microsoft.Management.Deployment/CatalogPackageMetadata.h @@ -4,6 +4,7 @@ #include "CatalogPackageMetadata.g.h" #include "PackageAgreement.h" #include "Documentation.h" +#include "Icon.h" namespace winrt::Microsoft::Management::Deployment::implementation { @@ -48,6 +49,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Management::Deployment::Documentation> Documentations(); + winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Management::Deployment::Icon> Icons(); + hstring ReleaseNotes(); hstring ReleaseNotesUrl(); @@ -61,6 +64,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation ::AppInstaller::Manifest::ManifestLocalization m_manifestLocalization; Windows::Foundation::Collections::IVector<winrt::Microsoft::Management::Deployment::PackageAgreement> m_packageAgreements{ nullptr }; Windows::Foundation::Collections::IVector<winrt::Microsoft::Management::Deployment::Documentation> m_documentations{ nullptr }; + Windows::Foundation::Collections::IVector<winrt::Microsoft::Management::Deployment::Icon> m_icons{ nullptr }; Windows::Foundation::Collections::IVector<hstring> m_tags{ nullptr }; #endif }; diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp @@ -312,11 +312,11 @@ namespace winrt::Microsoft::Management::Deployment::implementation { switch (scope) { - case winrt::Microsoft::Management::Deployment::PackageInstallScope::Any: + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any: return ::AppInstaller::Manifest::ScopeEnum::Unknown; - case winrt::Microsoft::Management::Deployment::PackageInstallScope::User: + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::User: return ::AppInstaller::Manifest::ScopeEnum::User; - case winrt::Microsoft::Management::Deployment::PackageInstallScope::System: + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::System: return ::AppInstaller::Manifest::ScopeEnum::Machine; } @@ -339,4 +339,75 @@ namespace winrt::Microsoft::Management::Deployment::implementation return Microsoft::Management::Deployment::ElevationRequirement::Unknown; } + + winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType) + { + switch (iconFileType) + { + case ::AppInstaller::Manifest::IconFileTypeEnum::Ico: + return Microsoft::Management::Deployment::IconFileType::Ico; + case ::AppInstaller::Manifest::IconFileTypeEnum::Jpeg: + return Microsoft::Management::Deployment::IconFileType::Jpeg; + case ::AppInstaller::Manifest::IconFileTypeEnum::Png: + return Microsoft::Management::Deployment::IconFileType::Png; + } + + return Microsoft::Management::Deployment::IconFileType::Unknown; + } + + winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution) + { + switch (iconResolution) + { + case ::AppInstaller::Manifest::IconResolutionEnum::Custom: + return Microsoft::Management::Deployment::IconResolution::Custom; + case ::AppInstaller::Manifest::IconResolutionEnum::Square16: + return Microsoft::Management::Deployment::IconResolution::Square16; + case ::AppInstaller::Manifest::IconResolutionEnum::Square20: + return Microsoft::Management::Deployment::IconResolution::Square20; + case ::AppInstaller::Manifest::IconResolutionEnum::Square24: + return Microsoft::Management::Deployment::IconResolution::Square24; + case ::AppInstaller::Manifest::IconResolutionEnum::Square30: + return Microsoft::Management::Deployment::IconResolution::Square30; + case ::AppInstaller::Manifest::IconResolutionEnum::Square32: + return Microsoft::Management::Deployment::IconResolution::Square32; + case ::AppInstaller::Manifest::IconResolutionEnum::Square36: + return Microsoft::Management::Deployment::IconResolution::Square36; + case ::AppInstaller::Manifest::IconResolutionEnum::Square40: + return Microsoft::Management::Deployment::IconResolution::Square40; + case ::AppInstaller::Manifest::IconResolutionEnum::Square48: + return Microsoft::Management::Deployment::IconResolution::Square48; + case ::AppInstaller::Manifest::IconResolutionEnum::Square60: + return Microsoft::Management::Deployment::IconResolution::Square60; + case ::AppInstaller::Manifest::IconResolutionEnum::Square64: + return Microsoft::Management::Deployment::IconResolution::Square64; + case ::AppInstaller::Manifest::IconResolutionEnum::Square72: + return Microsoft::Management::Deployment::IconResolution::Square72; + case ::AppInstaller::Manifest::IconResolutionEnum::Square80: + return Microsoft::Management::Deployment::IconResolution::Square80; + case ::AppInstaller::Manifest::IconResolutionEnum::Square96: + return Microsoft::Management::Deployment::IconResolution::Square96; + case ::AppInstaller::Manifest::IconResolutionEnum::Square256: + return Microsoft::Management::Deployment::IconResolution::Square256; + } + + return Microsoft::Management::Deployment::IconResolution::Custom; + } + + winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme) + { + switch (iconTheme) + { + case ::AppInstaller::Manifest::IconThemeEnum::Default: + return Microsoft::Management::Deployment::IconTheme::Default; + case ::AppInstaller::Manifest::IconThemeEnum::Light: + return Microsoft::Management::Deployment::IconTheme::Light; + case ::AppInstaller::Manifest::IconThemeEnum::Dark: + return Microsoft::Management::Deployment::IconTheme::Dark; + case ::AppInstaller::Manifest::IconThemeEnum::HighContrast: + return Microsoft::Management::Deployment::IconTheme::HighContrast; + } + + return Microsoft::Management::Deployment::IconTheme::Unknown; + } } diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h @@ -22,6 +22,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope); ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope); winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement); + winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType); + winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution); + winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme); #define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_) \ if constexpr (std::is_same_v<TStatus, winrt::Microsoft::Management::Deployment::InstallResultStatus>) \ diff --git a/src/Microsoft.Management.Deployment/Documentation.cpp b/src/Microsoft.Management.Deployment/Documentation.cpp @@ -3,7 +3,6 @@ #include "pch.h" #include "Documentation.g.cpp" #include "Documentation.h" -#include <wil\cppwinrt_wrl.h> namespace winrt::Microsoft::Management::Deployment::implementation { diff --git a/src/Microsoft.Management.Deployment/Icon.cpp b/src/Microsoft.Management.Deployment/Icon.cpp @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Icon.g.cpp" +#include "Icon.h" +#include "Converters.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void Icon::Initialize(::AppInstaller::Manifest::Icon icon) + { + m_icon = std::move(icon); + } + hstring Icon::Url() + { + return winrt::to_hstring(m_icon.Url); + } + IconFileType Icon::FileType() + { + return GetDeploymentIconFileType(m_icon.FileType); + } + IconResolution Icon::Resolution() + { + return GetDeploymentIconResolution(m_icon.Resolution); + } + IconTheme Icon::Theme() + { + return GetDeploymentIconTheme(m_icon.Theme); + } + com_array<uint8_t> Icon::Sha256() + { + return com_array<uint8_t>(m_icon.Sha256); + } +} diff --git a/src/Microsoft.Management.Deployment/Icon.h b/src/Microsoft.Management.Deployment/Icon.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Icon.g.h" +#include <winget/Manifest.h> + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct Icon : IconT<Icon> + { + Icon() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(::AppInstaller::Manifest::Icon icon); +#endif + + hstring Url(); + IconFileType FileType(); + IconResolution Resolution(); + IconTheme Theme(); + com_array<uint8_t> Sha256(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ::AppInstaller::Manifest::Icon m_icon{}; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/InstalledStatus.cpp b/src/Microsoft.Management.Deployment/InstalledStatus.cpp @@ -3,7 +3,6 @@ #include "pch.h" #include "InstalledStatus.h" #include "InstalledStatus.g.cpp" -#include <wil\cppwinrt_wrl.h> namespace winrt::Microsoft::Management::Deployment::implementation { diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj @@ -180,6 +180,7 @@ <ClInclude Include="FindPackagesOptions.h" /> <ClInclude Include="FindPackagesResult.h" /> <ClInclude Include="Helpers.h" /> + <ClInclude Include="Icon.h" /> <ClInclude Include="InstalledStatus.h" /> <ClInclude Include="InstallOptions.h" /> <ClInclude Include="InstallResult.h" /> @@ -213,6 +214,7 @@ <ClCompile Include="FindPackagesOptions.cpp" /> <ClCompile Include="FindPackagesResult.cpp" /> <ClCompile Include="Helpers.cpp" /> + <ClCompile Include="Icon.cpp" /> <ClCompile Include="InstalledStatus.cpp" /> <ClCompile Include="InstallOptions.cpp" /> <ClCompile Include="InstallResult.cpp" /> diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters @@ -32,6 +32,7 @@ <ClCompile Include="PackageInstallerInstalledStatus.cpp" /> <ClCompile Include="CheckInstalledStatusResult.cpp" /> <ClCompile Include="Documentation.cpp" /> + <ClCompile Include="Icon.cpp" /> </ItemGroup> <ItemGroup> <ClInclude Include="CatalogPackage.h" /> @@ -66,6 +67,7 @@ <ClInclude Include="CheckInstalledStatusResult.h" /> <ClInclude Include="CatalogPackageMetadata.h" /> <ClInclude Include="SourceAgreement.h" /> + <ClInclude Include="Icon.h" /> </ItemGroup> <ItemGroup> <Midl Include="PackageManager.idl" /> diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -876,7 +876,7 @@ namespace Microsoft.Management.Deployment } } - /// IMPLEMENTATION NOTE: Documentation from AppInstaller::Repository::Documentation + /// IMPLEMENTATION NOTE: Documentation from AppInstaller::Manifest::Documentation [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] runtimeclass Documentation { @@ -885,7 +885,64 @@ namespace Microsoft.Management.Deployment String DocumentUrl { get; }; } - /// IMPLEMENTATION NOTE: SourceAgreement from AppInstaller::Repository::SourceAgreement + /// Icon resolution + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconResolution + { + Custom, + Square16, + Square20, + Square24, + Square30, + Square32, + Square36, + Square40, + Square48, + Square60, + Square64, + Square72, + Square80, + Square96, + Square256, + }; + + /// Icon file type + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconFileType + { + Unknown, + Jpeg, + Png, + Ico, + }; + + /// Icon theme + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconTheme + { + Unknown, + Default, + Light, + Dark, + HighContrast, + }; + + /// IMPLEMENTATION NOTE: Icon from AppInstaller::Manifest::Icon + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass Icon + { + String Url { get; }; + + IconFileType FileType{ get; }; + + IconResolution Resolution{ get; }; + + IconTheme Theme{ get; }; + + UInt8[] Sha256 { get; }; + } + + /// IMPLEMENTATION NOTE: SourceAgreement from AppInstaller::Manifest::SourceAgreement [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] runtimeclass SourceAgreement { @@ -945,6 +1002,8 @@ namespace Microsoft.Management.Deployment Windows.Foundation.Collections.IVectorView<Documentation> Documentations { get; }; + Windows.Foundation.Collections.IVectorView<Icon> Icons { get; }; + String ReleaseNotes { get; }; String ReleaseNotesUrl { get; }; @@ -1068,6 +1127,8 @@ namespace Microsoft.Management.Deployment interface Windows.Foundation.Collections.IVectorView<PackageAgreement>; interface Windows.Foundation.Collections.IVector<Documentation>; interface Windows.Foundation.Collections.IVectorView<Documentation>; + interface Windows.Foundation.Collections.IVector<Icon>; + interface Windows.Foundation.Collections.IVectorView<Icon>; interface Windows.Foundation.Collections.IVector<CatalogPackageMetadata>; interface Windows.Foundation.Collections.IVectorView<CatalogPackageMetadata>; }