From fe960f110ed12f4670fa40e31220b02f197aa5f0 Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Mon, 30 Sep 2024 19:11:03 -0700 Subject: [PATCH 01/10] add initial changes --- common/Contracts/ILocalSettingsService.cs | 2 + common/DevHome.Common.csproj | 1 + common/Helpers/CommonConstants.cs | 15 +++ .../ExtensionJsonData/DevHomeExtension.cs | 15 +++ .../DevHomeExtensionJsonData.cs | 15 +++ .../JsonSourceGenerationContext.cs | 26 +++++ .../ExtensionJsonData/LocalizedProperties.cs | 15 +++ .../LocalizedPropertiesConverter.cs | 69 ++++++++++++ common/Models/ExtensionJsonData/Product.cs | 11 ++ common/Models/ExtensionJsonData/Properties.cs | 17 +++ .../ProviderSpecificProperty.cs | 11 ++ common/Services/IExtensionService.cs | 7 ++ common/Services/LocalSettingsService.cs | 6 + src/Assets/ExtensionInformation.json | 103 ++++++++++++++++++ .../Schemas/ExtensionInformation.schema.json | 94 ++++++++++++++++ src/DevHome.csproj | 11 +- src/Services/ExtensionService.cs | 32 +++++- src/Strings/en-us/Resources.resw | 28 +++++ test/DevHome.Test.csproj | 1 + test/FunctionalTests/ExtensionJsonTests.cs | 35 ++++++ test/FunctionalTests/ExtensionServiceTests.cs | 40 +++++++ .../ViewModels/ExtensionLibraryViewModel.cs | 84 ++++---------- 22 files changed, 576 insertions(+), 62 deletions(-) create mode 100644 common/Models/ExtensionJsonData/DevHomeExtension.cs create mode 100644 common/Models/ExtensionJsonData/DevHomeExtensionJsonData.cs create mode 100644 common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs create mode 100644 common/Models/ExtensionJsonData/LocalizedProperties.cs create mode 100644 common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs create mode 100644 common/Models/ExtensionJsonData/Product.cs create mode 100644 common/Models/ExtensionJsonData/Properties.cs create mode 100644 common/Models/ExtensionJsonData/ProviderSpecificProperty.cs create mode 100644 src/Assets/ExtensionInformation.json create mode 100644 src/Assets/Schemas/ExtensionInformation.schema.json create mode 100644 test/FunctionalTests/ExtensionJsonTests.cs create mode 100644 test/FunctionalTests/ExtensionServiceTests.cs diff --git a/common/Contracts/ILocalSettingsService.cs b/common/Contracts/ILocalSettingsService.cs index 0f5de30e26..0cf8791368 100644 --- a/common/Contracts/ILocalSettingsService.cs +++ b/common/Contracts/ILocalSettingsService.cs @@ -12,4 +12,6 @@ public interface ILocalSettingsService Task ReadSettingAsync(string key); Task SaveSettingAsync(string key, T value); + + public string GetPathToPackageLocation(); } diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj index f580193d5c..88651004ca 100644 --- a/common/DevHome.Common.csproj +++ b/common/DevHome.Common.csproj @@ -39,6 +39,7 @@ + all diff --git a/common/Helpers/CommonConstants.cs b/common/Helpers/CommonConstants.cs index 21572c9681..961e284725 100644 --- a/common/Helpers/CommonConstants.cs +++ b/common/Helpers/CommonConstants.cs @@ -1,6 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text.Json; +using DevHome.Common.Models.ExtensionJsonData; + namespace DevHome.Common.Helpers; public static class CommonConstants @@ -32,4 +35,16 @@ public static class CommonConstants #endif public const string HyperVWindowsOptionalFeatureName = "Microsoft-Hyper-V"; + + public static JsonSerializerOptions ExtensionJsonSerializerOptions => new() + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + TypeInfoResolver = new JsonSourceGenerationContext(), + TypeInfoResolverChain = { JsonSourceGenerationContext.Default }, + }; + + public static readonly string LocalExtensionJsonSchemaRelativeFilePath = @"Assets\Schemas\ExtensionInformation.schema.json"; + + public static readonly string LocalExtensionJsonRelativeFilePath = @"Assets\ExtensionInformation.json"; } diff --git a/common/Models/ExtensionJsonData/DevHomeExtension.cs b/common/Models/ExtensionJsonData/DevHomeExtension.cs new file mode 100644 index 0000000000..1aae22b27e --- /dev/null +++ b/common/Models/ExtensionJsonData/DevHomeExtension.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace DevHome.Common.Models.ExtensionJsonData; + +public class DevHomeExtension +{ + public required LocalizedProperties LocalizedProperties { get; set; } + + public required List SupportedProviderTypes { get; set; } = new(); + + public required List ProviderSpecificProperties { get; set; } = new(); +} diff --git a/common/Models/ExtensionJsonData/DevHomeExtensionJsonData.cs b/common/Models/ExtensionJsonData/DevHomeExtensionJsonData.cs new file mode 100644 index 0000000000..74056ddb56 --- /dev/null +++ b/common/Models/ExtensionJsonData/DevHomeExtensionJsonData.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace DevHome.Common.Models.ExtensionJsonData; + +/// +/// Root class that will contain the deserialized data located in the +/// src\Assets\Schemas\ExtensionInformation.schema.json file. +/// +public class DevHomeExtensionJsonData +{ + public List Products { get; set; } = new(); +} diff --git a/common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs b/common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs new file mode 100644 index 0000000000..b0bf040b23 --- /dev/null +++ b/common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using Json.Schema; + +namespace DevHome.Common.Models.ExtensionJsonData; + +/// +/// Used to generate C# classes from the extension json and extension json schema. +/// .Net 8 requires a JsonSerializerContext to be used with the JsonSerializerOptions when +/// deserializing JSON into objects. +/// See : https://learn.microsoft.com/dotnet/standard/serialization/system-text-json/source-generation?pivots=dotnet-8-0 +/// for more information +/// +[JsonSerializable(typeof(LocalizedProperties))] +[JsonSerializable(typeof(ProviderSpecificProperty))] +[JsonSerializable(typeof(Properties))] +[JsonSerializable(typeof(Product))] +[JsonSerializable(typeof(DevHomeExtensionJsonData))] +[JsonSerializable(typeof(DevHomeExtension))] +[JsonSerializable(typeof(JsonSchema))] +[JsonSerializable(typeof(EvaluationResults))] +public partial class JsonSourceGenerationContext : JsonSerializerContext +{ +} diff --git a/common/Models/ExtensionJsonData/LocalizedProperties.cs b/common/Models/ExtensionJsonData/LocalizedProperties.cs new file mode 100644 index 0000000000..2adcc71789 --- /dev/null +++ b/common/Models/ExtensionJsonData/LocalizedProperties.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Common.Models.ExtensionJsonData; + +public class LocalizedProperties +{ + public string DisplayNameKey { get; set; } = string.Empty; + + public string PublisherDisplayNameKey { get; set; } = string.Empty; + + public string DisplayName { get; set; } = string.Empty; + + public string PublisherDisplayName { get; set; } = string.Empty; +} diff --git a/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs b/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs new file mode 100644 index 0000000000..4eb251f69c --- /dev/null +++ b/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using DevHome.Common.Services; + +namespace DevHome.Common.Models.ExtensionJsonData; + +/// +/// Custom JSON converter for . +/// This should be added directly to a to handle the conversion of the localized key. +/// +public class LocalizedPropertiesConverter : JsonConverter +{ + private readonly IStringResource _stringResource; + + public LocalizedPropertiesConverter(IStringResource stringResource) + { + _stringResource = stringResource; + } + + public override LocalizedProperties Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var localizedProperties = new LocalizedProperties(); + + // Read the JSON and populate the properties + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + break; + } + + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetString(); + reader.Read(); // Move to the value + + if (reader.TokenType == JsonTokenType.Null) + { + continue; + } + + switch (propertyName) + { + case "DisplayNameKey": + localizedProperties.DisplayNameKey = reader.GetString()!; + break; + case "PublisherDisplayNameKey": + localizedProperties.PublisherDisplayNameKey = reader.GetString()!; + break; + } + } + } + + // Use the resource loader to populate DisplayName and PublisherDisplayName + localizedProperties.DisplayName = _stringResource.GetLocalized(localizedProperties.DisplayNameKey); + localizedProperties.PublisherDisplayName = _stringResource.GetLocalized(localizedProperties.PublisherDisplayNameKey); + + return localizedProperties; + } + + public override void Write(Utf8JsonWriter writer, LocalizedProperties value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/common/Models/ExtensionJsonData/Product.cs b/common/Models/ExtensionJsonData/Product.cs new file mode 100644 index 0000000000..8ee556abb6 --- /dev/null +++ b/common/Models/ExtensionJsonData/Product.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Common.Models.ExtensionJsonData; + +public class Product +{ + public required string ProductId { get; set; } + + public required Properties Properties { get; set; } +} diff --git a/common/Models/ExtensionJsonData/Properties.cs b/common/Models/ExtensionJsonData/Properties.cs new file mode 100644 index 0000000000..a7a2bc3fd6 --- /dev/null +++ b/common/Models/ExtensionJsonData/Properties.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace DevHome.Common.Models.ExtensionJsonData; + +public class Properties +{ + public required string PackageFamilyName { get; set; } + + public required bool SupportsWidgets { get; set; } + + public required LocalizedProperties LocalizedProperties { get; set; } + + public required List DevHomeExtensions { get; set; } = new(); +} diff --git a/common/Models/ExtensionJsonData/ProviderSpecificProperty.cs b/common/Models/ExtensionJsonData/ProviderSpecificProperty.cs new file mode 100644 index 0000000000..806b7e215c --- /dev/null +++ b/common/Models/ExtensionJsonData/ProviderSpecificProperty.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace DevHome.Common.Models.ExtensionJsonData; + +public class ProviderSpecificProperty +{ + public required LocalizedProperties LocalizedProperties { get; set; } + + public required string ProviderType { get; set; } +} diff --git a/common/Services/IExtensionService.cs b/common/Services/IExtensionService.cs index 298c19fe71..198b0df121 100644 --- a/common/Services/IExtensionService.cs +++ b/common/Services/IExtensionService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using DevHome.Common.Models.ExtensionJsonData; using Windows.Foundation; namespace DevHome.Common.Services; @@ -35,4 +36,10 @@ public interface IExtensionService /// The out of proc extension object /// True only if the extension was disabled. False otherwise. public Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionWrapper extension); + + /// + /// Gets known extension information from internal extension json file. + /// + /// An object that holds a list of extension information based on the internal json file. + public Task GetExtensionJsonDataAsync(); } diff --git a/common/Services/LocalSettingsService.cs b/common/Services/LocalSettingsService.cs index 17465e19ff..ea73b0cac2 100644 --- a/common/Services/LocalSettingsService.cs +++ b/common/Services/LocalSettingsService.cs @@ -9,6 +9,7 @@ using DevHome.Common.Helpers; using DevHome.Common.Models; using Microsoft.Extensions.Options; +using Windows.ApplicationModel; using Windows.Storage; namespace DevHome.Common.Services; @@ -106,4 +107,9 @@ public async Task SaveSettingAsync(string key, T value) await Task.Run(() => _fileService.Save(_applicationDataFolder, _localSettingsFile, _settings)); } } + + public string GetPathToPackageLocation() + { + return Package.Current.EffectivePath; + } } diff --git a/src/Assets/ExtensionInformation.json b/src/Assets/ExtensionInformation.json new file mode 100644 index 0000000000..1d00f1a343 --- /dev/null +++ b/src/Assets/ExtensionInformation.json @@ -0,0 +1,103 @@ +{ + "Products": [ + { + "ProductId": "9MV8F79FGXTR", + "Properties": { + "PackageFamilyName": "Microsoft.Windows.DevHomeAzureExtension_8wekyb3d8bbwe", + "SupportsWidgets": true, + "LocalizedProperties": { + "DisplayNameKey": "DevHomeAzureDisplayName", + "PublisherDisplayNameKey": "MicrosoftPublisher" + }, + "DevHomeExtensions": [ + { + "LocalizedProperties": { + "DisplayNameKey": "DevHomeAzureDisplayName" + }, + "SupportedProviderTypes": [ + "ComputeSystem", + "DeveloperId", + "QuickStartProject", + "Repository", + "Settings" + ], + "ProviderSpecificProperties": [ + { + "LocalizedProperties": { + "DisplayNameKey": "DevBoxProviderDisplayName" + }, + "ProviderType": "ComputeSystem" + } + ] + } + ] + } + }, + { + "ProductId": "9NZCC27PR6N6", + "Properties": { + "PackageFamilyName": "Microsoft.Windows.DevHomeGitHubExtension_8wekyb3d8bbwe", + "SupportsWidgets": true, + "LocalizedProperties": { + "DisplayNameKey": "DevHomeGitHubDisplayName", + "PublisherDisplayNameKey": "MicrosoftPublisher" + }, + "DevHomeExtensions": [ + { + "LocalizedProperties": { + "DisplayNameKey": "DevHomeGitHubDisplayName" + }, + "SupportedProviderTypes": [ + "DeveloperId", + "Repository", + "Settings" + ], + "ProviderSpecificProperties": [] + } + ] + } + }, + { + "ProductId": "9NZ845RW19RW", + "Properties": { + "PackageFamilyName": "Microsoft.DevHomeMicrosoftGameDevExtension_8wekyb3d8bbwe", + "SupportsWidgets": true, + "LocalizedProperties": { + "DisplayNameKey": "DevHomeGameDevDisplayName", + "PublisherDisplayNameKey": "MicrosoftPublisher" + }, + "DevHomeExtensions": [ + { + "LocalizedProperties": { + "DisplayNameKey": "DevHomeGameDevDisplayName" + }, + "SupportedProviderTypes": [ + "FeaturedApplications" + ], + "ProviderSpecificProperties": [] + } + ] + } + }, + { + "ProductId": "9NB9M5KZ8SLX", + "Properties": { + "PackageFamilyName": "9932MartCliment.WingetUIWidgets_g91dtg5srk15g", + "SupportsWidgets": true, + "LocalizedProperties": { + "DisplayNameKey": "WidgetsForUniGetUIDisplayName", + "PublisherDisplayNameKey": "WidgetsForUniGetUIPublisher" + }, + "DevHomeExtensions": [ + { + "LocalizedProperties": { + "DisplayNameKey": "WidgetsForUniGetUIDisplayName" + }, + "SupportedProviderTypes": [], + "ProviderSpecificProperties": [] + } + ] + } + } + ] +} \ No newline at end of file diff --git a/src/Assets/Schemas/ExtensionInformation.schema.json b/src/Assets/Schemas/ExtensionInformation.schema.json new file mode 100644 index 0000000000..e8539671be --- /dev/null +++ b/src/Assets/Schemas/ExtensionInformation.schema.json @@ -0,0 +1,94 @@ +{ + "$id": "", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "Required schema to allow users to discover non-installed extensions within Dev Home.", + "type": "object", + "properties": { + "Products": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ProductId": { "type": "string" }, + "Properties": { + "type": "object", + "properties": { + "PackageFamilyName": { "type": "string" }, + "LocalizedProperties": { + "type": "object", + "properties": { + "DisplayNameKey": { "type": "string" }, + "PublisherDisplayNameKey": { "type": "string" } + }, + "required": [ "DisplayNameKey", "PublisherDisplayNameKey" ] + }, + "DevHomeExtensions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LocalizedProperties": { + "type": "object", + "properties": { + "DisplayNameKey": { "type": "string" } + }, + "required": [ "DisplayNameKey" ] + }, + "SupportedProviderTypes": { + "type": "array", + "items": { + "enum": [ + "ComputeSystem", + "DeveloperId", + "FeaturedApplications", + "LocalRepository", + "QuickStartProject", + "Repository", + "Settings" + ] + } + }, + "ProviderSpecificProperties": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LocalizedProperties": { + "type": "object", + "properties": { + "DisplayNameKey": { "type": "string" } + }, + "required": [ "DisplayNameKey" ] + }, + "ProviderType": { + "type": "string", + "enum": [ + "ComputeSystem", + "DeveloperId", + "FeaturedApplications", + "LocalRepository", + "QuickStartProject", + "Repository", + "Settings" + ] + } + }, + "required": [ "LocalizedProperties", "ProviderType" ] + } + } + }, + "required": [ "LocalizedProperties", "SupportedProviderTypes", "ProviderSpecificProperties" ] + } + }, + "SupportsWidgets": { "type": "boolean" } + }, + "required": [ "PackageFamilyName", "LocalizedProperties", "DevHomeExtensions", "SupportsWidgets" ] + } + }, + "required": [ "ProductId", "Properties" ] + } + } + }, + "required": [ "Products" ], + "additionalProperties": false +} \ No newline at end of file diff --git a/src/DevHome.csproj b/src/DevHome.csproj index 222676547a..1d856b02b2 100644 --- a/src/DevHome.csproj +++ b/src/DevHome.csproj @@ -56,6 +56,15 @@ + + + Always + + + Always + + + @@ -84,7 +93,7 @@ - + diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index 80fa7101ad..bfcde7edd8 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text.Json; using DevHome.Common.Contracts; using DevHome.Common.Extensions; +using DevHome.Common.Models.ExtensionJsonData; using DevHome.Common.Services; using DevHome.ExtensionLibrary.TelemetryEvents; using DevHome.Models; @@ -14,6 +16,7 @@ using Windows.ApplicationModel.AppExtensions; using Windows.Foundation; using Windows.Foundation.Collections; +using static DevHome.Common.Helpers.CommonConstants; using static DevHome.Common.Helpers.ManagementInfrastructureHelper; namespace DevHome.Services; @@ -42,12 +45,21 @@ public class ExtensionService : IExtensionService, IDisposable private static readonly List _enabledExtensions = new(); private static readonly List _installedWidgetsPackageFamilyNames = new(); - public ExtensionService(ILocalSettingsService settingsService) + private readonly string _localExtensionJsonSchemaAbsoluteFilePath; + + private readonly string _localExtensionJsonAbsoluteFilePath; + + private readonly IStringResource _stringResource; + + public ExtensionService(ILocalSettingsService settingsService, IStringResource stringResource) { _catalog.PackageInstalling += Catalog_PackageInstalling; _catalog.PackageUninstalling += Catalog_PackageUninstalling; _catalog.PackageUpdating += Catalog_PackageUpdating; _localSettingsService = settingsService; + _localExtensionJsonSchemaAbsoluteFilePath = Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); + _localExtensionJsonAbsoluteFilePath = Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); + _stringResource = stringResource; } private void Catalog_PackageInstalling(PackageCatalog sender, PackageInstallingEventArgs args) @@ -404,4 +416,22 @@ public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionW return true; } + + public async Task GetExtensionJsonDataAsync() + { + try + { + _log.Information($"Get extension information from file: '{_localExtensionJsonAbsoluteFilePath}'"); + var extensionJson = await File.ReadAllTextAsync(_localExtensionJsonAbsoluteFilePath); + var serializerOptions = ExtensionJsonSerializerOptions; + serializerOptions.Converters.Add(new LocalizedPropertiesConverter(_stringResource)); + return JsonSerializer.Deserialize(extensionJson, serializerOptions); + } + catch (Exception ex) + { + _log.Error(ex, "Error retrieving extension json information"); + } + + return null; + } } diff --git a/src/Strings/en-us/Resources.resw b/src/Strings/en-us/Resources.resw index a724f7de7b..f759e4550f 100644 --- a/src/Strings/en-us/Resources.resw +++ b/src/Strings/en-us/Resources.resw @@ -349,4 +349,32 @@ SSH Keychain widget preview image + + Microsoft Dev Home Azure Extension (Preview) + {Locked="Microsoft", "Azure"} Display name for the Azure extension. + + + Microsoft Corporation + {Locked="Microsoft"} Display name for the Microsoft publisher. + + + Microsoft Dev Box + {Locked="Microsoft Dev Box"} Display name for the Dev Box provider. + + + Microsoft Dev Home GitHub Extension (Preview) + {Locked="Microsoft", "Azure"} Display name for the Azure extension. + + + Microsoft Dev Home Game Dev Extension (Preview) + {Locked="Microsoft"} Display name for the Game Dev extension. + + + Widgets for UniGetUI (formerly WingetUI) + {Locked="UniGetUI", "WingetUI"} Display name for the 'Widgets for UniGetUI' extension. + + + Martí Climent + {Locked="Martí Climent"} Display name for the 'Widgets for UniGetUI' publisher. + \ No newline at end of file diff --git a/test/DevHome.Test.csproj b/test/DevHome.Test.csproj index 58aa1d6e37..a79bf37b9d 100644 --- a/test/DevHome.Test.csproj +++ b/test/DevHome.Test.csproj @@ -15,6 +15,7 @@ + diff --git a/test/FunctionalTests/ExtensionJsonTests.cs b/test/FunctionalTests/ExtensionJsonTests.cs new file mode 100644 index 0000000000..b9e69a012c --- /dev/null +++ b/test/FunctionalTests/ExtensionJsonTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Nodes; +using Json.Schema; +using static DevHome.Common.Helpers.CommonConstants; + +namespace DevHome.Test.FunctionalTests; + +[TestClass] +public class ExtensionJsonTests +{ + [TestMethod] + public async Task ExtensionJsonIsValidatedBySchema() + { + string absoluteSchemaPath = Path.Combine(Directory.GetCurrentDirectory(), LocalExtensionJsonSchemaRelativeFilePath); + Assert.IsTrue(File.Exists(absoluteSchemaPath), $"The schema file '{absoluteSchemaPath}' does not exist."); + + string absoluteExtensionJsonPath = Path.Combine(Directory.GetCurrentDirectory(), LocalExtensionJsonRelativeFilePath); + Assert.IsTrue(File.Exists(absoluteExtensionJsonPath), $"The extension json file '{absoluteExtensionJsonPath}' does not exist."); + + var jsonSchema = await File.ReadAllTextAsync(absoluteSchemaPath); + var schema = JsonSchema.FromText(jsonSchema, ExtensionJsonSerializerOptions); + + var extensionJson = await File.ReadAllTextAsync(absoluteExtensionJsonPath); + var jsonNode = JsonNode.Parse(extensionJson); + var options = new EvaluationOptions + { + OutputFormat = OutputFormat.Hierarchical, + }; + var validationResult = schema.Evaluate(jsonNode, options); + + Assert.IsTrue(validationResult.IsValid); + } +} diff --git a/test/FunctionalTests/ExtensionServiceTests.cs b/test/FunctionalTests/ExtensionServiceTests.cs new file mode 100644 index 0000000000..87a4784a6b --- /dev/null +++ b/test/FunctionalTests/ExtensionServiceTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using DevHome.Common.Contracts; +using DevHome.Common.Models.ExtensionJsonData; +using DevHome.Common.Services; +using DevHome.Services; +using Moq; + +namespace DevHome.Test.FunctionalTests; + +[TestClass] +public class ExtensionServiceTests +{ + private readonly Mock _localSettingsService = new(); + + private readonly Mock _stringResouce = new(); + + [TestInitialize] + public void TestInitialize() + { + _stringResouce + .Setup(strResource => strResource.GetLocalized(It.IsAny(), It.IsAny())) + .Returns((string key, object[] args) => key); + + _localSettingsService.Setup(localSettingsService => + localSettingsService.GetPathToPackageLocation()) + .Returns(Directory.GetCurrentDirectory()); + } + + [TestMethod] + public async Task ExtensionServiceReturnsValidExtensionJsonDataObject() + { + var extensionService = new ExtensionService(_localSettingsService.Object, _stringResouce.Object); + var extensionJsonData = await extensionService.GetExtensionJsonDataAsync(); + Assert.IsNotNull(extensionJsonData); + + Assert.IsInstanceOfType(extensionJsonData, typeof(DevHomeExtensionJsonData)); + } +} diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs index 9da439e3e4..844d2e72f8 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs @@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.WinUI; using DevHome.Common.Extensions; +using DevHome.Common.Models.ExtensionJsonData; using DevHome.Common.Services; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; @@ -17,7 +18,6 @@ using Serilog; using Windows.ApplicationModel; using Windows.Data.Json; -using Windows.Storage; using static DevHome.Common.Helpers.CommonConstants; namespace DevHome.ExtensionLibrary.ViewModels; @@ -31,6 +31,8 @@ public partial class ExtensionLibraryViewModel : ObservableObject private readonly IExtensionService _extensionService; private readonly DispatcherQueue _dispatcherQueue; + private readonly IStringResource _stringResource; + // All internal Dev Home extensions that should allow users to enable/disable them, should add // their class Ids to this set. private readonly HashSet _internalClassIdsToBeShownInExtensionsPage = @@ -46,10 +48,14 @@ public partial class ExtensionLibraryViewModel : ObservableObject [ObservableProperty] private bool _shouldShowStoreError = false; - public ExtensionLibraryViewModel(IExtensionService extensionService, DispatcherQueue dispatcherQueue) + public ExtensionLibraryViewModel( + IExtensionService extensionService, + DispatcherQueue dispatcherQueue, + IStringResource stringResource) { _extensionService = extensionService; _dispatcherQueue = dispatcherQueue; + _stringResource = stringResource; StorePackagesList = new(); InstalledPackagesList = new(); @@ -133,32 +139,12 @@ private async Task GetInstalledPackagesAndExtensionsAsync() } } - private async Task GetStoreData() - { - var packagesFileContents = string.Empty; - var packagesFileName = "extensionResult.json"; - try - { - _log.Information($"Get packages file '{packagesFileName}'"); - var uri = new Uri($"ms-appx:///DevHome.ExtensionLibrary/Assets/{packagesFileName}"); - var file = await StorageFile.GetFileFromApplicationUriAsync(uri).AsTask().ConfigureAwait(false); - packagesFileContents = await FileIO.ReadTextAsync(file); - } - catch (Exception ex) - { - _log.Error(ex, "Error retrieving packages"); - ShouldShowStoreError = true; - } - - return packagesFileContents; - } - private async void GetAvailablePackages() { StorePackagesList.Clear(); - var storeData = await GetStoreData(); - if (string.IsNullOrEmpty(storeData)) + var extensionJsonData = await _extensionService.GetExtensionJsonDataAsync(); + if (extensionJsonData == null) { _log.Error("No package data found"); ShouldShowStoreError = true; @@ -167,45 +153,23 @@ private async void GetAvailablePackages() var tempStorePackagesList = new List(); - var jsonObj = JsonObject.Parse(storeData); - if (jsonObj != null) + foreach (var product in extensionJsonData.Products) { - var products = jsonObj.GetNamedArray("Products"); - foreach (var product in products) + // Don't show packages of already installed extensions as available. + if (IsAlreadyInstalled(product.Properties.PackageFamilyName)) { - var productObj = product.GetObject(); - var productId = productObj.GetNamedString("ProductId"); - - // Don't show self as available. - if (productId == DevHomeProductId) - { - continue; - } - - var title = string.Empty; - var publisher = string.Empty; - - var localizedProperties = productObj.GetNamedArray("LocalizedProperties"); - foreach (var localizedProperty in localizedProperties) - { - var propertyObject = localizedProperty.GetObject(); - title = propertyObject.GetNamedValue("ProductTitle").GetString(); - publisher = propertyObject.GetNamedValue("PublisherName").GetString(); - } - - var properties = productObj.GetNamedObject("Properties"); - var packageFamilyName = properties.GetNamedString("PackageFamilyName"); - - // Don't show packages of already installed extensions as available. - if (IsAlreadyInstalled(packageFamilyName)) - { - continue; - } - - _log.Information($"Found package: {productId}, {packageFamilyName}"); - var storePackage = new StorePackageViewModel(productId, title, publisher, packageFamilyName); - tempStorePackagesList.Add(storePackage); + continue; } + + _log.Information($"Found package: {product.ProductId}, {product.Properties.PackageFamilyName}"); + + var storePackage = new StorePackageViewModel( + product.ProductId, + _stringResource.GetLocalized(product.Properties.ResourceProperties.DisplayNameKey), + _stringResource.GetLocalized(product.Properties.ResourceProperties.PublisherDisplayNameKey), + product.Properties.PackageFamilyName); + + tempStorePackagesList.Add(storePackage); } tempStorePackagesList = tempStorePackagesList.OrderBy(storePackage => storePackage.Title).ToList(); From 6097ffe95a92106a3e7489519518222ae8d0905f Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Thu, 3 Oct 2024 16:12:37 -0700 Subject: [PATCH 02/10] update comments --- .../LocalizedPropertiesConverter.cs | 10 +++++----- src/DevHome.csproj | 2 +- src/Services/ExtensionService.cs | 2 +- .../ViewModels/ExtensionLibraryViewModel.cs | 14 +++----------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs b/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs index 4eb251f69c..2ac1c6b3c5 100644 --- a/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs +++ b/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs @@ -10,7 +10,8 @@ namespace DevHome.Common.Models.ExtensionJsonData; /// /// Custom JSON converter for . -/// This should be added directly to a to handle the conversion of the localized key. +/// This should be added directly to a to handle the conversion of a +/// localized key within the extension json to a value in the DevHome.pri resource file. /// public class LocalizedPropertiesConverter : JsonConverter { @@ -38,24 +39,23 @@ public override LocalizedProperties Read(ref Utf8JsonReader reader, Type typeToC var propertyName = reader.GetString(); reader.Read(); // Move to the value - if (reader.TokenType == JsonTokenType.Null) + if (reader.TokenType != JsonTokenType.String) { continue; } switch (propertyName) { - case "DisplayNameKey": + case nameof(LocalizedProperties.DisplayNameKey): localizedProperties.DisplayNameKey = reader.GetString()!; break; - case "PublisherDisplayNameKey": + case nameof(LocalizedProperties.PublisherDisplayNameKey): localizedProperties.PublisherDisplayNameKey = reader.GetString()!; break; } } } - // Use the resource loader to populate DisplayName and PublisherDisplayName localizedProperties.DisplayName = _stringResource.GetLocalized(localizedProperties.DisplayNameKey); localizedProperties.PublisherDisplayName = _stringResource.GetLocalized(localizedProperties.PublisherDisplayNameKey); diff --git a/src/DevHome.csproj b/src/DevHome.csproj index 0d88fe4980..8ad86a4784 100644 --- a/src/DevHome.csproj +++ b/src/DevHome.csproj @@ -93,7 +93,7 @@ - + diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index bfcde7edd8..ef4ed76e3d 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -421,7 +421,7 @@ public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionW { try { - _log.Information($"Get extension information from file: '{_localExtensionJsonAbsoluteFilePath}'"); + _log.Information($"Getting extension information from file: '{_localExtensionJsonAbsoluteFilePath}'"); var extensionJson = await File.ReadAllTextAsync(_localExtensionJsonAbsoluteFilePath); var serializerOptions = ExtensionJsonSerializerOptions; serializerOptions.Converters.Add(new LocalizedPropertiesConverter(_stringResource)); diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs index 844d2e72f8..04958c07b7 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs @@ -10,14 +10,12 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.WinUI; using DevHome.Common.Extensions; -using DevHome.Common.Models.ExtensionJsonData; using DevHome.Common.Services; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.Windows.DevHome.SDK; using Serilog; using Windows.ApplicationModel; -using Windows.Data.Json; using static DevHome.Common.Helpers.CommonConstants; namespace DevHome.ExtensionLibrary.ViewModels; @@ -31,8 +29,6 @@ public partial class ExtensionLibraryViewModel : ObservableObject private readonly IExtensionService _extensionService; private readonly DispatcherQueue _dispatcherQueue; - private readonly IStringResource _stringResource; - // All internal Dev Home extensions that should allow users to enable/disable them, should add // their class Ids to this set. private readonly HashSet _internalClassIdsToBeShownInExtensionsPage = @@ -48,14 +44,10 @@ public partial class ExtensionLibraryViewModel : ObservableObject [ObservableProperty] private bool _shouldShowStoreError = false; - public ExtensionLibraryViewModel( - IExtensionService extensionService, - DispatcherQueue dispatcherQueue, - IStringResource stringResource) + public ExtensionLibraryViewModel(IExtensionService extensionService, DispatcherQueue dispatcherQueue) { _extensionService = extensionService; _dispatcherQueue = dispatcherQueue; - _stringResource = stringResource; StorePackagesList = new(); InstalledPackagesList = new(); @@ -165,8 +157,8 @@ private async void GetAvailablePackages() var storePackage = new StorePackageViewModel( product.ProductId, - _stringResource.GetLocalized(product.Properties.ResourceProperties.DisplayNameKey), - _stringResource.GetLocalized(product.Properties.ResourceProperties.PublisherDisplayNameKey), + product.Properties.LocalizedProperties.DisplayName, + product.Properties.LocalizedProperties.PublisherDisplayName, product.Properties.PackageFamilyName); tempStorePackagesList.Add(storePackage); From cbeefb61aea717f7196ee0fa5c256c220cf22cf3 Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Thu, 3 Oct 2024 17:31:55 -0700 Subject: [PATCH 03/10] fix build --- tools/SetupFlow/DevHome.SetupFlow.UnitTest/BaseSetupFlowTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/SetupFlow/DevHome.SetupFlow.UnitTest/BaseSetupFlowTest.cs b/tools/SetupFlow/DevHome.SetupFlow.UnitTest/BaseSetupFlowTest.cs index b88b11e919..34fc3dfaec 100644 --- a/tools/SetupFlow/DevHome.SetupFlow.UnitTest/BaseSetupFlowTest.cs +++ b/tools/SetupFlow/DevHome.SetupFlow.UnitTest/BaseSetupFlowTest.cs @@ -64,7 +64,7 @@ private IHost CreateTestHost() services.AddSingleton(ThemeSelectorService!.Object); services.AddSingleton(StringResource.Object); services.AddSingleton(new SetupFlowOrchestrator(null)); - services.AddSingleton(new ExtensionService(LocalSettingsService.Object)); + services.AddSingleton(new ExtensionService(LocalSettingsService.Object, StringResource.Object)); // App-management view models services.AddTransient(); From 9e43c25c77251d713c16cb667809e29d3f2d51e8 Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Thu, 3 Oct 2024 17:57:59 -0700 Subject: [PATCH 04/10] move absolutepath calls to actual method --- src/Services/ExtensionService.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index ef4ed76e3d..f7a9f3fe1a 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -45,11 +45,11 @@ public class ExtensionService : IExtensionService, IDisposable private static readonly List _enabledExtensions = new(); private static readonly List _installedWidgetsPackageFamilyNames = new(); - private readonly string _localExtensionJsonSchemaAbsoluteFilePath; + private readonly IStringResource _stringResource; - private readonly string _localExtensionJsonAbsoluteFilePath; + private string? _localExtensionJsonSchemaAbsoluteFilePath; - private readonly IStringResource _stringResource; + private string? _localExtensionJsonAbsoluteFilePath; public ExtensionService(ILocalSettingsService settingsService, IStringResource stringResource) { @@ -57,8 +57,6 @@ public ExtensionService(ILocalSettingsService settingsService, IStringResource s _catalog.PackageUninstalling += Catalog_PackageUninstalling; _catalog.PackageUpdating += Catalog_PackageUpdating; _localSettingsService = settingsService; - _localExtensionJsonSchemaAbsoluteFilePath = Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); - _localExtensionJsonAbsoluteFilePath = Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); _stringResource = stringResource; } @@ -421,6 +419,9 @@ public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionW { try { + _localExtensionJsonSchemaAbsoluteFilePath ??= Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); + _localExtensionJsonAbsoluteFilePath ??= Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); + _log.Information($"Getting extension information from file: '{_localExtensionJsonAbsoluteFilePath}'"); var extensionJson = await File.ReadAllTextAsync(_localExtensionJsonAbsoluteFilePath); var serializerOptions = ExtensionJsonSerializerOptions; From 85db8ad34404dc2fffdc949f73452de24739582d Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Fri, 4 Oct 2024 15:46:48 -0700 Subject: [PATCH 05/10] update based on comments and use lazy loading --- ...Data.cs => DevHomeExtensionContentData.cs} | 2 +- .../JsonSourceGenerationContext.cs | 2 +- common/Services/IExtensionService.cs | 2 +- .../Schemas/ExtensionInformation.schema.json | 1 + src/Services/ExtensionService.cs | 27 ++++++++++++------- test/FunctionalTests/ExtensionServiceTests.cs | 2 +- 6 files changed, 23 insertions(+), 13 deletions(-) rename common/Models/ExtensionJsonData/{DevHomeExtensionJsonData.cs => DevHomeExtensionContentData.cs} (90%) diff --git a/common/Models/ExtensionJsonData/DevHomeExtensionJsonData.cs b/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs similarity index 90% rename from common/Models/ExtensionJsonData/DevHomeExtensionJsonData.cs rename to common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs index 74056ddb56..5b03b91829 100644 --- a/common/Models/ExtensionJsonData/DevHomeExtensionJsonData.cs +++ b/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs @@ -9,7 +9,7 @@ namespace DevHome.Common.Models.ExtensionJsonData; /// Root class that will contain the deserialized data located in the /// src\Assets\Schemas\ExtensionInformation.schema.json file. /// -public class DevHomeExtensionJsonData +public class DevHomeExtensionContentData { public List Products { get; set; } = new(); } diff --git a/common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs b/common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs index b0bf040b23..a93ad10e59 100644 --- a/common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs +++ b/common/Models/ExtensionJsonData/JsonSourceGenerationContext.cs @@ -17,7 +17,7 @@ namespace DevHome.Common.Models.ExtensionJsonData; [JsonSerializable(typeof(ProviderSpecificProperty))] [JsonSerializable(typeof(Properties))] [JsonSerializable(typeof(Product))] -[JsonSerializable(typeof(DevHomeExtensionJsonData))] +[JsonSerializable(typeof(DevHomeExtensionContentData))] [JsonSerializable(typeof(DevHomeExtension))] [JsonSerializable(typeof(JsonSchema))] [JsonSerializable(typeof(EvaluationResults))] diff --git a/common/Services/IExtensionService.cs b/common/Services/IExtensionService.cs index 198b0df121..89368ff0e9 100644 --- a/common/Services/IExtensionService.cs +++ b/common/Services/IExtensionService.cs @@ -41,5 +41,5 @@ public interface IExtensionService /// Gets known extension information from internal extension json file. /// /// An object that holds a list of extension information based on the internal json file. - public Task GetExtensionJsonDataAsync(); + public Task GetExtensionJsonDataAsync(); } diff --git a/src/Assets/Schemas/ExtensionInformation.schema.json b/src/Assets/Schemas/ExtensionInformation.schema.json index e8539671be..60d28729f3 100644 --- a/src/Assets/Schemas/ExtensionInformation.schema.json +++ b/src/Assets/Schemas/ExtensionInformation.schema.json @@ -61,6 +61,7 @@ "required": [ "DisplayNameKey" ] }, "ProviderType": { + "$comment": "Enum values should be kept in sync with the provider type enum values located in the Dev Home SDK.", "type": "string", "enum": [ "ComputeSystem", diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index f7a9f3fe1a..e3fdeccb43 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -47,9 +47,9 @@ public class ExtensionService : IExtensionService, IDisposable private readonly IStringResource _stringResource; - private string? _localExtensionJsonSchemaAbsoluteFilePath; + private readonly Lazy _localExtensionJsonSchemaAbsoluteFilePath; - private string? _localExtensionJsonAbsoluteFilePath; + private readonly Lazy _localExtensionJsonAbsoluteFilePath; public ExtensionService(ILocalSettingsService settingsService, IStringResource stringResource) { @@ -58,6 +58,18 @@ public ExtensionService(ILocalSettingsService settingsService, IStringResource s _catalog.PackageUpdating += Catalog_PackageUpdating; _localSettingsService = settingsService; _stringResource = stringResource; + _localExtensionJsonSchemaAbsoluteFilePath = new Lazy(GetExtensionJsonSchemaAbsoluteFilePath); + _localExtensionJsonAbsoluteFilePath = new Lazy(GetExtensionJsonAbsoluteFilePath); + } + + private string GetExtensionJsonSchemaAbsoluteFilePath() + { + return Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonSchemaRelativeFilePath); + } + + private string GetExtensionJsonAbsoluteFilePath() + { + return Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); } private void Catalog_PackageInstalling(PackageCatalog sender, PackageInstallingEventArgs args) @@ -415,18 +427,15 @@ public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionW return true; } - public async Task GetExtensionJsonDataAsync() + public async Task GetExtensionJsonDataAsync() { try { - _localExtensionJsonSchemaAbsoluteFilePath ??= Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); - _localExtensionJsonAbsoluteFilePath ??= Path.Combine(_localSettingsService.GetPathToPackageLocation(), LocalExtensionJsonRelativeFilePath); - - _log.Information($"Getting extension information from file: '{_localExtensionJsonAbsoluteFilePath}'"); - var extensionJson = await File.ReadAllTextAsync(_localExtensionJsonAbsoluteFilePath); + _log.Information($"Getting extension information from file: '{_localExtensionJsonAbsoluteFilePath.Value}'"); + var extensionJson = await File.ReadAllTextAsync(_localExtensionJsonAbsoluteFilePath.Value); var serializerOptions = ExtensionJsonSerializerOptions; serializerOptions.Converters.Add(new LocalizedPropertiesConverter(_stringResource)); - return JsonSerializer.Deserialize(extensionJson, serializerOptions); + return JsonSerializer.Deserialize(extensionJson, serializerOptions); } catch (Exception ex) { diff --git a/test/FunctionalTests/ExtensionServiceTests.cs b/test/FunctionalTests/ExtensionServiceTests.cs index 87a4784a6b..aa1bb550b3 100644 --- a/test/FunctionalTests/ExtensionServiceTests.cs +++ b/test/FunctionalTests/ExtensionServiceTests.cs @@ -35,6 +35,6 @@ public async Task ExtensionServiceReturnsValidExtensionJsonDataObject() var extensionJsonData = await extensionService.GetExtensionJsonDataAsync(); Assert.IsNotNull(extensionJsonData); - Assert.IsInstanceOfType(extensionJsonData, typeof(DevHomeExtensionJsonData)); + Assert.IsInstanceOfType(extensionJsonData, typeof(DevHomeExtensionContentData)); } } From 54f64724264948a46639ce640e33dc0f4b8d86bd Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Fri, 4 Oct 2024 15:55:35 -0700 Subject: [PATCH 06/10] update comments in correct place --- common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs | 3 ++- src/Assets/Schemas/ExtensionInformation.schema.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs b/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs index 5b03b91829..bf27993f3a 100644 --- a/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs +++ b/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs @@ -7,7 +7,8 @@ namespace DevHome.Common.Models.ExtensionJsonData; /// /// Root class that will contain the deserialized data located in the -/// src\Assets\Schemas\ExtensionInformation.schema.json file. +/// src\Assets\ExtensionInformation.json file. Its schema is located in +/// src\Assets\Schemas\ExtensionInformation.schema.json. /// public class DevHomeExtensionContentData { diff --git a/src/Assets/Schemas/ExtensionInformation.schema.json b/src/Assets/Schemas/ExtensionInformation.schema.json index 60d28729f3..6dc7602ec3 100644 --- a/src/Assets/Schemas/ExtensionInformation.schema.json +++ b/src/Assets/Schemas/ExtensionInformation.schema.json @@ -35,6 +35,7 @@ "required": [ "DisplayNameKey" ] }, "SupportedProviderTypes": { + "$comment": "Enum values should be kept in sync with the provider type enum values located in the Dev Home SDK.", "type": "array", "items": { "enum": [ @@ -61,7 +62,6 @@ "required": [ "DisplayNameKey" ] }, "ProviderType": { - "$comment": "Enum values should be kept in sync with the provider type enum values located in the Dev Home SDK.", "type": "string", "enum": [ "ComputeSystem", From b8a1dd91d2d07a6ff4d9f1695f3e1faecb017299 Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Mon, 7 Oct 2024 14:47:26 -0700 Subject: [PATCH 07/10] update based on comments --- common/DevHome.Common.csproj | 1 + .../ExtensionJsonData/DevHomeExtension.cs | 2 +- .../DevHomeExtensionContentData.cs | 2 + .../ExtensionJsonData/LocalizedProperties.cs | 4 -- .../LocalizedPropertiesConverter.cs | 4 -- common/Models/ExtensionJsonData/Properties.cs | 24 ++++++++++- .../Contracts/IWinGetPackage.cs | 8 ++++ .../Models/WinGetPackage.cs | 3 ++ src/Assets/ExtensionInformation.json | 37 ++++------------ .../Schemas/ExtensionInformation.schema.json | 28 ++++++++----- src/Services/ExtensionService.cs | 42 ++++++++++++++++++- test/FunctionalTests/ExtensionServiceTests.cs | 27 +++++++++++- .../ViewModels/ExtensionLibraryViewModel.cs | 22 +++++++--- .../ViewModels/StorePackageViewModel.cs | 16 ++++--- .../Views/ExtensionLibraryView.xaml | 9 +++- .../BaseSetupFlowTest.cs | 3 +- 16 files changed, 168 insertions(+), 64 deletions(-) diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj index b541b8014d..27c8d340ef 100644 --- a/common/DevHome.Common.csproj +++ b/common/DevHome.Common.csproj @@ -61,6 +61,7 @@ + diff --git a/common/Models/ExtensionJsonData/DevHomeExtension.cs b/common/Models/ExtensionJsonData/DevHomeExtension.cs index 1aae22b27e..75d3580e82 100644 --- a/common/Models/ExtensionJsonData/DevHomeExtension.cs +++ b/common/Models/ExtensionJsonData/DevHomeExtension.cs @@ -7,7 +7,7 @@ namespace DevHome.Common.Models.ExtensionJsonData; public class DevHomeExtension { - public required LocalizedProperties LocalizedProperties { get; set; } + public LocalizedProperties LocalizedProperties { get; set; } = new(); public required List SupportedProviderTypes { get; set; } = new(); diff --git a/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs b/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs index bf27993f3a..bc758c6c96 100644 --- a/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs +++ b/common/Models/ExtensionJsonData/DevHomeExtensionContentData.cs @@ -12,5 +12,7 @@ namespace DevHome.Common.Models.ExtensionJsonData; /// public class DevHomeExtensionContentData { + public List ProductIds { get; set; } = new(); + public List Products { get; set; } = new(); } diff --git a/common/Models/ExtensionJsonData/LocalizedProperties.cs b/common/Models/ExtensionJsonData/LocalizedProperties.cs index 2adcc71789..36df91155e 100644 --- a/common/Models/ExtensionJsonData/LocalizedProperties.cs +++ b/common/Models/ExtensionJsonData/LocalizedProperties.cs @@ -7,9 +7,5 @@ public class LocalizedProperties { public string DisplayNameKey { get; set; } = string.Empty; - public string PublisherDisplayNameKey { get; set; } = string.Empty; - public string DisplayName { get; set; } = string.Empty; - - public string PublisherDisplayName { get; set; } = string.Empty; } diff --git a/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs b/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs index 2ac1c6b3c5..e47d70fb17 100644 --- a/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs +++ b/common/Models/ExtensionJsonData/LocalizedPropertiesConverter.cs @@ -49,15 +49,11 @@ public override LocalizedProperties Read(ref Utf8JsonReader reader, Type typeToC case nameof(LocalizedProperties.DisplayNameKey): localizedProperties.DisplayNameKey = reader.GetString()!; break; - case nameof(LocalizedProperties.PublisherDisplayNameKey): - localizedProperties.PublisherDisplayNameKey = reader.GetString()!; - break; } } } localizedProperties.DisplayName = _stringResource.GetLocalized(localizedProperties.DisplayNameKey); - localizedProperties.PublisherDisplayName = _stringResource.GetLocalized(localizedProperties.PublisherDisplayNameKey); return localizedProperties; } diff --git a/common/Models/ExtensionJsonData/Properties.cs b/common/Models/ExtensionJsonData/Properties.cs index a7a2bc3fd6..60540048c6 100644 --- a/common/Models/ExtensionJsonData/Properties.cs +++ b/common/Models/ExtensionJsonData/Properties.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using DevHome.Services.WindowsPackageManager.Contracts; namespace DevHome.Common.Models.ExtensionJsonData; @@ -11,7 +12,28 @@ public class Properties public required bool SupportsWidgets { get; set; } - public required LocalizedProperties LocalizedProperties { get; set; } + public IWinGetPackage? WinGetPackage { get; set; } + + public string Description { get; set; } = string.Empty; + + public string PublisherName { get; set; } = string.Empty; + + public string ProductTitle { get; set; } = string.Empty; public required List DevHomeExtensions { get; set; } = new(); + + public string GetLocalizedDescription() + { + return WinGetPackage?.Description ?? Description; + } + + public string GetLocalizedPublisherName() + { + return WinGetPackage?.PublisherName ?? PublisherName; + } + + public string GetLocalizedProductTitle() + { + return WinGetPackage?.Name ?? ProductTitle; + } } diff --git a/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackage.cs b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackage.cs index 26efcd4932..399b3b8deb 100644 --- a/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackage.cs +++ b/services/DevHome.Services.WindowsPackageManager/Contracts/IWinGetPackage.cs @@ -144,6 +144,14 @@ public bool IsElevationRequired get; } + /// + /// Gets the package description. + /// + public string Description + { + get; + } + /// /// Gets the package uri of this package /// diff --git a/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackage.cs b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackage.cs index 41be6cc3a2..1e1981b926 100644 --- a/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackage.cs +++ b/services/DevHome.Services.WindowsPackageManager/Models/WinGetPackage.cs @@ -42,6 +42,7 @@ public WinGetPackage(ILogger logger, CatalogPackage package, bool requiresElevat PublisherUrl = GetMetadataValue(package, metadata => new Uri(metadata.PublisherUrl), nameof(CatalogPackageMetadata.PublisherUrl), null); PublisherName = GetMetadataValue(package, metadata => metadata.Publisher, nameof(CatalogPackageMetadata.Publisher), null); InstallationNotes = GetMetadataValue(package, metadata => metadata.InstallationNotes, nameof(CatalogPackageMetadata.InstallationNotes), null); + Description = GetMetadataValue(package, metadata => metadata.Description, nameof(CatalogPackageMetadata.Description), null); } public string Id { get; } @@ -76,6 +77,8 @@ public WinGetPackage(ILogger logger, CatalogPackage package, bool requiresElevat public bool IsElevationRequired { get; } + public string Description { get; } + public WinGetPackageUri GetUri(string installVersion = null) { var uriOptions = string.IsNullOrEmpty(installVersion) ? null : new WinGetPackageUriOptions(installVersion); diff --git a/src/Assets/ExtensionInformation.json b/src/Assets/ExtensionInformation.json index 1d00f1a343..077c13df66 100644 --- a/src/Assets/ExtensionInformation.json +++ b/src/Assets/ExtensionInformation.json @@ -1,19 +1,15 @@ { + "ProductIds": [ "9MV8F79FGXTR", "9NZCC27PR6N6", "9NZ845RW19RW", "9NB9M5KZ8SLX" ], "Products": [ { "ProductId": "9MV8F79FGXTR", "Properties": { + "PublisherName": "Microsoft Corporation", + "ProductTitle": "Dev Home Azure Extension (Preview)", "PackageFamilyName": "Microsoft.Windows.DevHomeAzureExtension_8wekyb3d8bbwe", "SupportsWidgets": true, - "LocalizedProperties": { - "DisplayNameKey": "DevHomeAzureDisplayName", - "PublisherDisplayNameKey": "MicrosoftPublisher" - }, "DevHomeExtensions": [ { - "LocalizedProperties": { - "DisplayNameKey": "DevHomeAzureDisplayName" - }, "SupportedProviderTypes": [ "ComputeSystem", "DeveloperId", @@ -36,17 +32,12 @@ { "ProductId": "9NZCC27PR6N6", "Properties": { + "PublisherName": "Microsoft Corporation", + "ProductTitle": "Dev Home GitHub Extension (Preview)", "PackageFamilyName": "Microsoft.Windows.DevHomeGitHubExtension_8wekyb3d8bbwe", "SupportsWidgets": true, - "LocalizedProperties": { - "DisplayNameKey": "DevHomeGitHubDisplayName", - "PublisherDisplayNameKey": "MicrosoftPublisher" - }, "DevHomeExtensions": [ { - "LocalizedProperties": { - "DisplayNameKey": "DevHomeGitHubDisplayName" - }, "SupportedProviderTypes": [ "DeveloperId", "Repository", @@ -60,17 +51,12 @@ { "ProductId": "9NZ845RW19RW", "Properties": { + "PublisherName": "Microsoft Corporation", + "ProductTitle": "Microsoft Game Dev Extension (Preview)", "PackageFamilyName": "Microsoft.DevHomeMicrosoftGameDevExtension_8wekyb3d8bbwe", "SupportsWidgets": true, - "LocalizedProperties": { - "DisplayNameKey": "DevHomeGameDevDisplayName", - "PublisherDisplayNameKey": "MicrosoftPublisher" - }, "DevHomeExtensions": [ { - "LocalizedProperties": { - "DisplayNameKey": "DevHomeGameDevDisplayName" - }, "SupportedProviderTypes": [ "FeaturedApplications" ], @@ -82,17 +68,12 @@ { "ProductId": "9NB9M5KZ8SLX", "Properties": { + "PublisherName": "Martí Climent", + "ProductTitle": "Widgets for UniGetUI (formerly WingetUI)", "PackageFamilyName": "9932MartCliment.WingetUIWidgets_g91dtg5srk15g", "SupportsWidgets": true, - "LocalizedProperties": { - "DisplayNameKey": "WidgetsForUniGetUIDisplayName", - "PublisherDisplayNameKey": "WidgetsForUniGetUIPublisher" - }, "DevHomeExtensions": [ { - "LocalizedProperties": { - "DisplayNameKey": "WidgetsForUniGetUIDisplayName" - }, "SupportedProviderTypes": [], "ProviderSpecificProperties": [] } diff --git a/src/Assets/Schemas/ExtensionInformation.schema.json b/src/Assets/Schemas/ExtensionInformation.schema.json index 6dc7602ec3..e3292ddba3 100644 --- a/src/Assets/Schemas/ExtensionInformation.schema.json +++ b/src/Assets/Schemas/ExtensionInformation.schema.json @@ -4,6 +4,13 @@ "description": "Required schema to allow users to discover non-installed extensions within Dev Home.", "type": "object", "properties": { + "ProductIds": { + "$comment": "The Microsoft store productIds listed here should match only one product in the products list below.", + "type": "array", + "items": { + "type": "string" + } + }, "Products": { "type": "array", "items": { @@ -13,21 +20,22 @@ "Properties": { "type": "object", "properties": { - "PackageFamilyName": { "type": "string" }, - "LocalizedProperties": { - "type": "object", - "properties": { - "DisplayNameKey": { "type": "string" }, - "PublisherDisplayNameKey": { "type": "string" } - }, - "required": [ "DisplayNameKey", "PublisherDisplayNameKey" ] + "PublisherName": { + "$comment": "Fallback property in case WinGet is unavailable on the users computer to get the localized publisher name.", + "type": "string" + }, + "ProductTitle": { + "$comment": "Fallback property in case WinGet is unavailable on the users computer to get the localized product title.", + "type": "string" }, + "PackageFamilyName": { "type": "string" }, "DevHomeExtensions": { "type": "array", "items": { "type": "object", "properties": { "LocalizedProperties": { + "$comment": "This is only necessary if a package contains multiple extensions. Dev Home, by default will assume a package only contains a single extension and use the packages product title from WinGet as the display name for the first extension in the list.", "type": "object", "properties": { "DisplayNameKey": { "type": "string" } @@ -78,12 +86,12 @@ } } }, - "required": [ "LocalizedProperties", "SupportedProviderTypes", "ProviderSpecificProperties" ] + "required": [ "SupportedProviderTypes", "ProviderSpecificProperties" ] } }, "SupportsWidgets": { "type": "boolean" } }, - "required": [ "PackageFamilyName", "LocalizedProperties", "DevHomeExtensions", "SupportsWidgets" ] + "required": [ "ProductTitle", "PublisherName", "PackageFamilyName", "DevHomeExtensions", "SupportsWidgets" ] } }, "required": [ "ProductId", "Properties" ] diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index e3fdeccb43..98c144a647 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -8,6 +8,8 @@ using DevHome.Common.Services; using DevHome.ExtensionLibrary.TelemetryEvents; using DevHome.Models; +using DevHome.Services.WindowsPackageManager.Contracts; +using DevHome.Services.WindowsPackageManager.Models; using DevHome.Telemetry; using Microsoft.UI.Xaml; using Microsoft.Windows.DevHome.SDK; @@ -36,6 +38,8 @@ public class ExtensionService : IExtensionService, IDisposable private readonly ILocalSettingsService _localSettingsService; + private readonly IWinGet _winGet; + private bool _disposedValue; private const string CreateInstanceProperty = "CreateInstance"; @@ -51,13 +55,17 @@ public class ExtensionService : IExtensionService, IDisposable private readonly Lazy _localExtensionJsonAbsoluteFilePath; - public ExtensionService(ILocalSettingsService settingsService, IStringResource stringResource) + public ExtensionService( + ILocalSettingsService settingsService, + IStringResource stringResource, + IWinGet winGet) { _catalog.PackageInstalling += Catalog_PackageInstalling; _catalog.PackageUninstalling += Catalog_PackageUninstalling; _catalog.PackageUpdating += Catalog_PackageUpdating; _localSettingsService = settingsService; _stringResource = stringResource; + _winGet = winGet; _localExtensionJsonSchemaAbsoluteFilePath = new Lazy(GetExtensionJsonSchemaAbsoluteFilePath); _localExtensionJsonAbsoluteFilePath = new Lazy(GetExtensionJsonAbsoluteFilePath); } @@ -428,6 +436,12 @@ public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionW } public async Task GetExtensionJsonDataAsync() + { + // offload interaction Interaction with WinGet to background thread. + return await Task.Run(GetExtensionJsonDataInternalAsync); + } + + private async Task GetExtensionJsonDataInternalAsync() { try { @@ -435,7 +449,31 @@ public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionW var extensionJson = await File.ReadAllTextAsync(_localExtensionJsonAbsoluteFilePath.Value); var serializerOptions = ExtensionJsonSerializerOptions; serializerOptions.Converters.Add(new LocalizedPropertiesConverter(_stringResource)); - return JsonSerializer.Deserialize(extensionJson, serializerOptions); + var contentData = JsonSerializer.Deserialize(extensionJson, serializerOptions); + + if (contentData == null) + { + throw new InvalidDataException($"Unable to deserialize json in {_localExtensionJsonAbsoluteFilePath.Value}"); + } + + var winGetIds = new List(); + var winGetIdToProductMap = new Dictionary(); + contentData.Products.ForEach(product => + { + winGetIdToProductMap.Add(product.ProductId, product); + winGetIds.Add(_winGet.CreateMsStoreCatalogPackageUri(product.ProductId)); + }); + + var winGetPackages = await _winGet.GetPackagesAsync(winGetIds); + foreach (var package in winGetPackages) + { + if (winGetIdToProductMap.TryGetValue(package.Id, out var product)) + { + product.Properties.WinGetPackage = package; + } + } + + return contentData; } catch (Exception ex) { diff --git a/test/FunctionalTests/ExtensionServiceTests.cs b/test/FunctionalTests/ExtensionServiceTests.cs index aa1bb550b3..2e66ef5526 100644 --- a/test/FunctionalTests/ExtensionServiceTests.cs +++ b/test/FunctionalTests/ExtensionServiceTests.cs @@ -5,6 +5,8 @@ using DevHome.Common.Models.ExtensionJsonData; using DevHome.Common.Services; using DevHome.Services; +using DevHome.Services.WindowsPackageManager.Contracts; +using DevHome.Services.WindowsPackageManager.Models; using Moq; namespace DevHome.Test.FunctionalTests; @@ -16,9 +18,32 @@ public class ExtensionServiceTests private readonly Mock _stringResouce = new(); + private readonly Mock _winGet = new(); + + private readonly WinGetPackageUri _packageUri = new("x-ms-winget://msstore/9MV8F79FGXTR"); + + private readonly Mock _winGetPackage = new(); + + private async Task> MockGetPackagesAsync() + { + await Task.CompletedTask; + return new List() { _winGetPackage.Object }; + } + [TestInitialize] public void TestInitialize() { + // Setup WinGet to return at least one valid package. + _winGet + .Setup(manager => manager.CreateMsStoreCatalogPackageUri(It.IsAny())) + .Returns(_packageUri); + + _winGet + .Setup(manager => manager.GetPackagesAsync(It.IsAny>())) + .Returns(MockGetPackagesAsync); + + _winGetPackage.Setup(package => package.Id).Returns("9MV8F79FGXTR"); + _stringResouce .Setup(strResource => strResource.GetLocalized(It.IsAny(), It.IsAny())) .Returns((string key, object[] args) => key); @@ -31,7 +56,7 @@ public void TestInitialize() [TestMethod] public async Task ExtensionServiceReturnsValidExtensionJsonDataObject() { - var extensionService = new ExtensionService(_localSettingsService.Object, _stringResouce.Object); + var extensionService = new ExtensionService(_localSettingsService.Object, _stringResouce.Object, _winGet.Object); var extensionJsonData = await extensionService.GetExtensionJsonDataAsync(); Assert.IsNotNull(extensionJsonData); diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs index 04958c07b7..3f2f85e3e0 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/ExtensionLibraryViewModel.cs @@ -44,6 +44,12 @@ public partial class ExtensionLibraryViewModel : ObservableObject [ObservableProperty] private bool _shouldShowStoreError = false; + [ObservableProperty] + private bool _shouldShowAvailableExtensionsProgressRing = false; + + [ObservableProperty] + private bool _shouldShowNoExtensionsAvailableMessage = false; + public ExtensionLibraryViewModel(IExtensionService extensionService, DispatcherQueue dispatcherQueue) { _extensionService = extensionService; @@ -63,6 +69,8 @@ public async Task GetUpdatesButtonAsync() public async Task LoadedAsync() { await GetInstalledPackagesAndExtensionsAsync(); + ShouldShowNoExtensionsAvailableMessage = false; + ShouldShowAvailableExtensionsProgressRing = true; GetAvailablePackages(); if (_extensionService != null) @@ -138,6 +146,7 @@ private async void GetAvailablePackages() var extensionJsonData = await _extensionService.GetExtensionJsonDataAsync(); if (extensionJsonData == null) { + ShouldShowAvailableExtensionsProgressRing = false; _log.Error("No package data found"); ShouldShowStoreError = true; return; @@ -155,11 +164,7 @@ private async void GetAvailablePackages() _log.Information($"Found package: {product.ProductId}, {product.Properties.PackageFamilyName}"); - var storePackage = new StorePackageViewModel( - product.ProductId, - product.Properties.LocalizedProperties.DisplayName, - product.Properties.LocalizedProperties.PublisherDisplayName, - product.Properties.PackageFamilyName); + var storePackage = new StorePackageViewModel(product); tempStorePackagesList.Add(storePackage); } @@ -169,6 +174,13 @@ private async void GetAvailablePackages() { StorePackagesList.Add(storePackage); } + + ShouldShowAvailableExtensionsProgressRing = false; + + if (StorePackagesList.Count == 0) + { + ShouldShowNoExtensionsAvailableMessage = true; + } } private bool IsAlreadyInstalled(string packageFamilyName) => diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs index c8f2780e26..f90b2c327b 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs @@ -5,12 +5,15 @@ using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using DevHome.Common.Models.ExtensionJsonData; using Windows.System; namespace DevHome.ExtensionLibrary.ViewModels; public partial class StorePackageViewModel : ObservableObject { + private readonly Product _product; + [ObservableProperty] private string _productId; @@ -26,13 +29,14 @@ public partial class StorePackageViewModel : ObservableObject [ObservableProperty] private string _automationInstallPfn; - public StorePackageViewModel(string productId, string title, string publisher, string packageFamilyName) + public StorePackageViewModel(Product product) { - _productId = productId; - _title = title; - _publisher = publisher; - _packageFamilyName = packageFamilyName; - _automationInstallPfn = $"Install_{packageFamilyName}"; + _product = product; + _productId = product.ProductId; + _title = product.Properties.GetLocalizedProductTitle(); + _publisher = product.Properties.GetLocalizedPublisherName(); + _packageFamilyName = product.Properties.PackageFamilyName; + _automationInstallPfn = $"Install_{_packageFamilyName}"; } [RelayCommand] diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml index 29d092eeeb..43e847b919 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Views/ExtensionLibraryView.xaml @@ -145,9 +145,16 @@ + (ThemeSelectorService!.Object); services.AddSingleton(StringResource.Object); services.AddSingleton(new SetupFlowOrchestrator(null)); - services.AddSingleton(new ExtensionService(LocalSettingsService.Object, StringResource.Object)); + services.AddSingleton( + new ExtensionService(LocalSettingsService.Object, StringResource.Object, WindowsPackageManager.Object)); // App-management view models services.AddTransient(); From 790de9a721dc3e2174c014871e51fcc38f42b41d Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Mon, 7 Oct 2024 14:53:42 -0700 Subject: [PATCH 08/10] update based on comments 2 --- src/Strings/en-us/Resources.resw | 24 ------- .../Assets/extensionResult.json | 62 ------------------- 2 files changed, 86 deletions(-) delete mode 100644 tools/ExtensionLibrary/DevHome.ExtensionLibrary/Assets/extensionResult.json diff --git a/src/Strings/en-us/Resources.resw b/src/Strings/en-us/Resources.resw index f759e4550f..bd1e5926e2 100644 --- a/src/Strings/en-us/Resources.resw +++ b/src/Strings/en-us/Resources.resw @@ -349,32 +349,8 @@ SSH Keychain widget preview image - - Microsoft Dev Home Azure Extension (Preview) - {Locked="Microsoft", "Azure"} Display name for the Azure extension. - - - Microsoft Corporation - {Locked="Microsoft"} Display name for the Microsoft publisher. - Microsoft Dev Box {Locked="Microsoft Dev Box"} Display name for the Dev Box provider. - - Microsoft Dev Home GitHub Extension (Preview) - {Locked="Microsoft", "Azure"} Display name for the Azure extension. - - - Microsoft Dev Home Game Dev Extension (Preview) - {Locked="Microsoft"} Display name for the Game Dev extension. - - - Widgets for UniGetUI (formerly WingetUI) - {Locked="UniGetUI", "WingetUI"} Display name for the 'Widgets for UniGetUI' extension. - - - Martí Climent - {Locked="Martí Climent"} Display name for the 'Widgets for UniGetUI' publisher. - \ No newline at end of file diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Assets/extensionResult.json b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Assets/extensionResult.json deleted file mode 100644 index 583f418c24..0000000000 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/Assets/extensionResult.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "ProductIds": [ "9NB9M5KZ8SLX", "9NZ845RW19RW", "9MV8F79FGXTR", "9NZCC27PR6N6" ], - "Products": [ - { - "LocalizedProperties": [ - { - "PublisherName": "Martí Climent", - "ProductDescription": "Widgets for the Windows Widgets and Dev Home applications that will help you quickly manage your software updates without having to launch UniGetUI.\n\nWARNING: This application requires UniGetUI to be installed and running on your system. This application does NOT perform any of the updates, but rather it interfaces with UniGetUI to keep your packages up-to-date.", - "ProductTitle": "Widgets for UniGetUI (formerly WingetUI)", - "ShortDescription": "Handy widgets to manage your packages without having to open UniGetUI" - } - ], - "ProductId": "9NB9M5KZ8SLX", - "Properties": { - "PackageFamilyName": "9932MartCliment.WingetUIWidgets_g91dtg5srk15g" - } - }, - { - "LocalizedProperties": [ - { - "PublisherName": "Microsoft Corporation", - "ProductDescription": "Dev Home Microsoft Game Dev Extension provides Microsoft Game Development integration into the Dev Home experience. This extension provides gaming development package recommendations for Dev Home's machine configuration tool as well as game development widgets for the dashboard. You will need Dev Home installed in order to use this extension.", - "ProductTitle": "Microsoft Game Dev Extension (Preview)", - "ShortDescription": "" - } - ], - "ProductId": "9NZ845RW19RW", - "Properties": { - "PackageFamilyName": "Microsoft.DevHomeMicrosoftGameDevExtension_8wekyb3d8bbwe" - } - }, - { - "LocalizedProperties": [ - { - "PublisherName": "Microsoft Corporation", - "ProductDescription": "Dev Home Azure Extension provides Azure integration into the Dev Home experience. This extension provides repository recommendations for Dev Home's machine configuration tool as well as Azure widgets for the dashboard. You will need Dev Home installed in order to use this extension.\n\n\nThis is an open source project and we welcome community participation. To participate, please visit https://github.com/microsoft/devhomeazureextension", - "ProductTitle": "Dev Home Azure Extension (Preview)", - "ShortDescription": "" - } - ], - "ProductId": "9MV8F79FGXTR", - "Properties": { - "PackageFamilyName": "Microsoft.Windows.DevHomeAzureExtension_8wekyb3d8bbwe" - } - }, - { - "LocalizedProperties": [ - { - "PublisherName": "Microsoft Corporation", - "ProductDescription": "Dev Home GitHub Extension provides GitHub integration into the Dev Home experience. This extension provides repository recommendations for Dev Home's machine configuration tool as well as GitHub widgets for the dashboard. You will need Dev Home installed in order to use this extension.\n\n\nThis is an open source project and we welcome community participation. To participate, please visit https://github.com/microsoft/devhomegithubextension", - "ProductTitle": "Dev Home GitHub Extension (Preview)", - "ShortDescription": "" - } - ], - "ProductId": "9NZCC27PR6N6", - "Properties": { - "PackageFamilyName": "Microsoft.Windows.DevHomeGitHubExtension_8wekyb3d8bbwe" - } - } - ], - "TotalResultCount": 4 -} \ No newline at end of file From 0320ce3167e95ccd8c0d313ff5b7900d8caf6562 Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Mon, 7 Oct 2024 19:36:24 -0700 Subject: [PATCH 09/10] fix build --- common/DevHome.Common.csproj | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj index 27c8d340ef..e1a621d0e4 100644 --- a/common/DevHome.Common.csproj +++ b/common/DevHome.Common.csproj @@ -61,7 +61,13 @@ - + + + From 77e8af84a385c6637d1acc0f01808761195dd6c1 Mon Sep 17 00:00:00 2001 From: Branden Bonaby Date: Tue, 8 Oct 2024 01:00:37 -0700 Subject: [PATCH 10/10] fix build 2 --- common/DevHome.Common.csproj | 7 ------- common/Models/ExtensionJsonData/Properties.cs | 18 ------------------ src/Services/ExtensionService.cs | 4 +++- .../ViewModels/StorePackageViewModel.cs | 4 ++-- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/common/DevHome.Common.csproj b/common/DevHome.Common.csproj index e1a621d0e4..b541b8014d 100644 --- a/common/DevHome.Common.csproj +++ b/common/DevHome.Common.csproj @@ -61,13 +61,6 @@ - - - diff --git a/common/Models/ExtensionJsonData/Properties.cs b/common/Models/ExtensionJsonData/Properties.cs index 60540048c6..0c54bea1b9 100644 --- a/common/Models/ExtensionJsonData/Properties.cs +++ b/common/Models/ExtensionJsonData/Properties.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections.Generic; -using DevHome.Services.WindowsPackageManager.Contracts; namespace DevHome.Common.Models.ExtensionJsonData; @@ -12,8 +11,6 @@ public class Properties public required bool SupportsWidgets { get; set; } - public IWinGetPackage? WinGetPackage { get; set; } - public string Description { get; set; } = string.Empty; public string PublisherName { get; set; } = string.Empty; @@ -21,19 +18,4 @@ public class Properties public string ProductTitle { get; set; } = string.Empty; public required List DevHomeExtensions { get; set; } = new(); - - public string GetLocalizedDescription() - { - return WinGetPackage?.Description ?? Description; - } - - public string GetLocalizedPublisherName() - { - return WinGetPackage?.PublisherName ?? PublisherName; - } - - public string GetLocalizedProductTitle() - { - return WinGetPackage?.Name ?? ProductTitle; - } } diff --git a/src/Services/ExtensionService.cs b/src/Services/ExtensionService.cs index 98c144a647..d674d030cc 100644 --- a/src/Services/ExtensionService.cs +++ b/src/Services/ExtensionService.cs @@ -469,7 +469,9 @@ public async Task DisableExtensionIfWindowsFeatureNotAvailable(IExtensionW { if (winGetIdToProductMap.TryGetValue(package.Id, out var product)) { - product.Properties.WinGetPackage = package; + product.Properties.Description = package.Description; + product.Properties.PublisherName = package.PublisherName; + product.Properties.ProductTitle = package.Name; } } diff --git a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs index f90b2c327b..93d8ce1626 100644 --- a/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs +++ b/tools/ExtensionLibrary/DevHome.ExtensionLibrary/ViewModels/StorePackageViewModel.cs @@ -33,8 +33,8 @@ public StorePackageViewModel(Product product) { _product = product; _productId = product.ProductId; - _title = product.Properties.GetLocalizedProductTitle(); - _publisher = product.Properties.GetLocalizedPublisherName(); + _title = product.Properties.ProductTitle; + _publisher = product.Properties.PublisherName; _packageFamilyName = product.Properties.PackageFamilyName; _automationInstallPfn = $"Install_{_packageFamilyName}"; }