From bfd4a436de9901f5f4645a0b00ce94f2c39db3b0 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Thu, 22 Feb 2024 21:23:47 +0100 Subject: [PATCH] Profile service - Migrate profiles before importing them --- .../DryIoc/ContainerExtensions.cs | 1 + .../Storage/Interfaces/IProfileMigration.cs | 9 ++ .../ProfileMigrators/M0001NodeProviders.cs | 90 +++++++++++++++++++ .../Services/Storage/ProfileService.cs | 32 ++++++- .../Profile/ProfileConfigurationEntity.cs | 1 + 5 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 src/Artemis.Core/Services/Storage/Interfaces/IProfileMigration.cs create mode 100644 src/Artemis.Core/Services/Storage/ProfileMigrators/M0001NodeProviders.cs diff --git a/src/Artemis.Core/DryIoc/ContainerExtensions.cs b/src/Artemis.Core/DryIoc/ContainerExtensions.cs index 44d377e8a..337fb1def 100644 --- a/src/Artemis.Core/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.Core/DryIoc/ContainerExtensions.cs @@ -36,6 +36,7 @@ public static class ContainerExtensions // Bind migrations container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton, nonPublicServiceTypes: true); + container.RegisterMany(coreAssembly, type => type.IsAssignableTo(), Reuse.Singleton, nonPublicServiceTypes: true); container.RegisterMany(coreAssembly, type => type.IsAssignableTo(), Reuse.Singleton); container.Register(Reuse.Singleton); diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileMigration.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileMigration.cs new file mode 100644 index 000000000..7365014e8 --- /dev/null +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileMigration.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json.Linq; + +namespace Artemis.Core.Services; + +internal interface IProfileMigration +{ + int Version { get; } + void Migrate(JObject profileJson); +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ProfileMigrators/M0001NodeProviders.cs b/src/Artemis.Core/Services/Storage/ProfileMigrators/M0001NodeProviders.cs new file mode 100644 index 000000000..c55fde0a9 --- /dev/null +++ b/src/Artemis.Core/Services/Storage/ProfileMigrators/M0001NodeProviders.cs @@ -0,0 +1,90 @@ +using Newtonsoft.Json.Linq; + +namespace Artemis.Core.Services.ProfileMigrators; + +/// +/// Migrates nodes to be provider-based. +/// This requires giving them a ProviderId and updating the their namespaces to match the namespace of the new plugin. +/// +internal class M0001NodeProviders : IProfileMigration +{ + /// + public int Version => 1; + + /// + public void Migrate(JObject profileJson) + { + JArray? folders = (JArray?) profileJson["Folders"]?["$values"]; + JArray? layers = (JArray?) profileJson["Layers"]?["$values"]; + + if (folders != null) + { + foreach (JToken folder in folders) + { + MigrateProfileElement(folder); + } + } + + if (layers != null) + { + foreach (JToken layer in layers) + { + MigrateProfileElement(layer); + MigratePropertyGroup(layer["GeneralPropertyGroup"]); + MigratePropertyGroup(layer["TransformPropertyGroup"]); + MigratePropertyGroup(layer["LayerBrush"]?["PropertyGroup"]); + } + } + } + + private void MigrateProfileElement(JToken profileElement) + { + JArray? layerEffects = (JArray?) profileElement["LayerEffects"]?["$values"]; + if (layerEffects != null) + { + foreach (JToken layerEffect in layerEffects) + MigratePropertyGroup(layerEffect["PropertyGroup"]); + } + + JToken? displayCondition = profileElement["DisplayCondition"]; + if (displayCondition != null) + MigrateNodeScript(displayCondition["Script"]); + } + + private void MigratePropertyGroup(JToken? propertyGroup) + { + if (propertyGroup == null || !propertyGroup.HasValues) + return; + + JArray? properties = (JArray?) propertyGroup["Properties"]?["$values"]; + JArray? propertyGroups = (JArray?) propertyGroup["PropertyGroups"]?["$values"]; + + if (properties != null) + { + foreach (JToken property in properties) + MigrateNodeScript(property["DataBinding"]?["NodeScript"]); + } + + if (propertyGroups != null) + { + foreach (JToken childPropertyGroup in propertyGroups) + MigratePropertyGroup(childPropertyGroup); + } + } + + private void MigrateNodeScript(JToken? nodeScript) + { + if (nodeScript == null || !nodeScript.HasValues) + return; + + JArray? nodes = (JArray?) nodeScript["Nodes"]?["$values"]; + if (nodes == null) + return; + + foreach (JToken node in nodes) + { + node["Type"] = node["Type"]?.Value()?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes"); + node["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index f93a4f152..e3d0b6fe7 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -10,6 +10,7 @@ using Artemis.Core.Modules; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Repositories.Interfaces; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Serilog; using SkiaSharp; @@ -24,9 +25,10 @@ internal class ProfileService : IProfileService private readonly List _pendingKeyboardEvents = new(); private readonly List _profileCategories; private readonly IProfileRepository _profileRepository; + private readonly List _profileMigrators; private readonly List _renderExceptions = new(); private readonly List _updateExceptions = new(); - + private DateTime _lastRenderExceptionLog; private DateTime _lastUpdateExceptionLog; @@ -35,13 +37,15 @@ internal class ProfileService : IProfileService IPluginManagementService pluginManagementService, IInputService inputService, IDeviceService deviceService, - IProfileRepository profileRepository) + IProfileRepository profileRepository, + List profileMigrators) { _logger = logger; _profileCategoryRepository = profileCategoryRepository; _pluginManagementService = pluginManagementService; _deviceService = deviceService; _profileRepository = profileRepository; + _profileMigrators = profileMigrators; _profileCategories = new List(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order)); _deviceService.LedsChanged += DeviceServiceOnLedsChanged; @@ -58,7 +62,7 @@ internal class ProfileService : IProfileService public ProfileConfiguration? FocusProfile { get; set; } public ProfileElement? FocusProfileElement { get; set; } public bool UpdateFocusProfile { get; set; } - + public bool ProfileRenderingDisabled { get; set; } /// @@ -461,7 +465,12 @@ internal class ProfileService : IProfileService await using Stream profileStream = profileEntry.Open(); using StreamReader profileReader = new(profileStream); - ProfileEntity? profileEntity = JsonConvert.DeserializeObject(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings); + JObject? profileJson = JsonConvert.DeserializeObject(await profileReader.ReadToEndAsync(), IProfileService.ExportSettings); + + // Before deserializing, apply any pending migrations + MigrateProfile(configurationEntity, profileJson); + + ProfileEntity? profileEntity = profileJson?.ToObject(JsonSerializer.Create(IProfileService.ExportSettings)); if (profileEntity == null) throw new ArtemisCoreException("Could not import profile, failed to deserialize profile.json"); @@ -545,6 +554,21 @@ internal class ProfileService : IProfileService } } + private void MigrateProfile(ProfileConfigurationEntity configurationEntity, JObject? profileJson) + { + if (profileJson == null) + return; + + foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version)) + { + if (profileMigrator.Version <= configurationEntity.Version) + continue; + + profileMigrator.Migrate(profileJson); + configurationEntity.Version = profileMigrator.Version; + } + } + /// /// Populates all missing LEDs on all currently active profiles /// diff --git a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs index 276b6ec8d..941c9057b 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs @@ -26,4 +26,5 @@ public class ProfileConfigurationEntity public Guid ProfileId { get; set; } public bool FadeInAndOut { get; set; } + public int Version { get; set; } } \ No newline at end of file