From 10850adb24884f52fbfaf9924bb7b98ec4caa0bf Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Tue, 5 Mar 2024 22:08:29 +0100 Subject: [PATCH 01/14] Storage - Introduce SQLite+EF Core --- .../DryIoc/ContainerExtensions.cs | 2 - .../Surface/ArtemisDeviceInputIdentifier.cs | 4 +- .../Plugins/Settings/IPluginSetting.cs | 6 + .../Plugins/Settings/PluginSetting.cs | 11 +- .../InputProviderIdentifierEventArgs.cs | 4 +- .../Services/Input/InputProvider.cs | 2 +- .../Interfaces/IPluginManagementService.cs | 14 +-- .../Services/PluginManagementService.cs | 62 +-------- src/Artemis.Storage/Artemis.Storage.csproj | 1 + src/Artemis.Storage/ArtemisDbContext.cs | 37 ++++++ .../Entities/General/QueuedActionEntity.cs | 18 --- .../Entities/Plugins/PluginSettingEntity.cs | 3 +- .../Entities/Profile/ProfileCategoryEntity.cs | 9 +- .../Profile/ProfileConfigurationEntity.cs | 2 +- .../Entities/Surface/DeviceEntity.cs | 2 +- .../Entities/Workshop/EntryEntity.cs | 3 +- .../Migrations/IStorageMigration.cs | 9 -- .../Migrations/Storage/M0020AvaloniaReset.cs | 19 --- .../Migrations/Storage/M0021GradientNodes.cs | 90 ------------- .../Storage/M0022TransitionNodes.cs | 86 ------------- .../Storage/M0023LayoutProviders.cs | 34 ----- .../Migrations/Storage/M0024NodeProviders.cs | 107 ---------------- .../M0025NodeProvidersProfileConfig.cs | 46 ------- .../Migrations/Storage/M0026NodeStorage.cs | 119 ------------------ .../Repositories/DeviceRepository.cs | 28 ++--- .../Repositories/EntryRepository.cs | 31 ++--- .../Interfaces/IDeviceRepository.cs | 3 +- .../Interfaces/IEntryRepository.cs | 3 +- .../Interfaces/IPluginRepository.cs | 4 +- .../Interfaces/IProfileCategoryRepository.cs | 6 +- .../Interfaces/IProfileRepository.cs | 10 +- .../Interfaces/IQueuedActionRepository.cs | 14 --- .../Repositories/PluginRepository.cs | 42 +++---- .../Repositories/ProfileCategoryRepository.cs | 72 +++-------- .../Repositories/ProfileRepository.cs | 30 ++--- .../Repositories/QueuedActionRepository.cs | 57 --------- .../Repositories/ReleaseRepository.cs | 20 +-- .../StorageMigrationService.cs | 53 -------- .../Models/InstalledEntry.cs | 34 +++-- .../Services/WorkshopService.cs | 2 +- src/Directory.Packages.props | 2 + 41 files changed, 198 insertions(+), 903 deletions(-) create mode 100644 src/Artemis.Storage/ArtemisDbContext.cs delete mode 100644 src/Artemis.Storage/Entities/General/QueuedActionEntity.cs delete mode 100644 src/Artemis.Storage/Migrations/IStorageMigration.cs delete mode 100644 src/Artemis.Storage/Migrations/Storage/M0020AvaloniaReset.cs delete mode 100644 src/Artemis.Storage/Migrations/Storage/M0021GradientNodes.cs delete mode 100644 src/Artemis.Storage/Migrations/Storage/M0022TransitionNodes.cs delete mode 100644 src/Artemis.Storage/Migrations/Storage/M0023LayoutProviders.cs delete mode 100644 src/Artemis.Storage/Migrations/Storage/M0024NodeProviders.cs delete mode 100644 src/Artemis.Storage/Migrations/Storage/M0025NodeProvidersProfileConfig.cs delete mode 100644 src/Artemis.Storage/Migrations/Storage/M0026NodeStorage.cs delete mode 100644 src/Artemis.Storage/Repositories/Interfaces/IQueuedActionRepository.cs delete mode 100644 src/Artemis.Storage/Repositories/QueuedActionRepository.cs delete mode 100644 src/Artemis.Storage/StorageMigrationService.cs diff --git a/src/Artemis.Core/DryIoc/ContainerExtensions.cs b/src/Artemis.Core/DryIoc/ContainerExtensions.cs index 8aa35b65f..5e057e049 100644 --- a/src/Artemis.Core/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.Core/DryIoc/ContainerExtensions.cs @@ -31,11 +31,9 @@ public static void RegisterCore(this IContainer container) // Bind storage container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton); - container.Register(Reuse.Singleton); container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton); // Bind migrations - container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton, nonPublicServiceTypes: true); container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton, nonPublicServiceTypes: true); container.RegisterMany(coreAssembly, type => type.IsAssignableTo(), Reuse.Singleton); diff --git a/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs b/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs index 6d101cdf7..410134624 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDeviceInputIdentifier.cs @@ -14,7 +14,7 @@ public class ArtemisDeviceInputIdentifier /// used by /// /// A value used to identify the device - internal ArtemisDeviceInputIdentifier(string inputProvider, object identifier) + internal ArtemisDeviceInputIdentifier(string inputProvider, string identifier) { InputProvider = inputProvider; Identifier = identifier; @@ -28,5 +28,5 @@ internal ArtemisDeviceInputIdentifier(string inputProvider, object identifier) /// /// Gets or sets a value used to identify the device /// - public object Identifier { get; set; } + public string Identifier { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs b/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs index 57a454420..a33844c1f 100644 --- a/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json; namespace Artemis.Core; @@ -7,6 +8,11 @@ namespace Artemis.Core; /// public interface IPluginSetting { + /// + /// The JSON serializer options used when serializing settings + /// + protected static readonly JsonSerializerOptions SerializerOptions = CoreJson.GetJsonSerializerOptions(); + /// /// The name of the setting, unique to this plugin /// diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs index 8f14d542f..390566c76 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs @@ -1,5 +1,6 @@ using System; using System.Text.Json; +using System.Text.Json.Nodes; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; @@ -23,7 +24,7 @@ internal PluginSetting(IPluginRepository pluginRepository, PluginSettingEntity p Name = pluginSettingEntity.Name; try { - _value = CoreJson.Deserialize(pluginSettingEntity.Value)!; + _value = pluginSettingEntity.Value.Deserialize(IPluginSetting.SerializerOptions) ?? default!; } catch (JsonException) { @@ -76,7 +77,7 @@ protected internal virtual void OnSettingSaved() public string Name { get; } /// - public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value; + public bool HasChanged => !JsonNode.DeepEquals(JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions), _pluginSettingEntity.Value); /// public bool AutoSave { get; set; } @@ -84,7 +85,7 @@ protected internal virtual void OnSettingSaved() /// public void RejectChanges() { - Value = CoreJson.Deserialize(_pluginSettingEntity.Value); + Value = _pluginSettingEntity.Value.Deserialize(IPluginSetting.SerializerOptions) ?? default!; } /// @@ -93,8 +94,8 @@ public void Save() if (!HasChanged) return; - _pluginSettingEntity.Value = CoreJson.Serialize(Value); - _pluginRepository.SaveSetting(_pluginSettingEntity); + _pluginSettingEntity.Value = JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions) ?? new JsonObject(); + _pluginRepository.SaveChanges(); OnSettingSaved(); } diff --git a/src/Artemis.Core/Services/Input/Events/InputProviderIdentifierEventArgs.cs b/src/Artemis.Core/Services/Input/Events/InputProviderIdentifierEventArgs.cs index cbc41d950..a6660b1a3 100644 --- a/src/Artemis.Core/Services/Input/Events/InputProviderIdentifierEventArgs.cs +++ b/src/Artemis.Core/Services/Input/Events/InputProviderIdentifierEventArgs.cs @@ -12,7 +12,7 @@ public class InputProviderIdentifierEventArgs : EventArgs /// /// A value that can be used to identify this device /// The type of device this identifier belongs to - public InputProviderIdentifierEventArgs(object identifier, InputDeviceType deviceType) + public InputProviderIdentifierEventArgs(string identifier, InputDeviceType deviceType) { Identifier = identifier; DeviceType = deviceType; @@ -21,7 +21,7 @@ public InputProviderIdentifierEventArgs(object identifier, InputDeviceType devic /// /// Gets a value that can be used to identify this device /// - public object Identifier { get; } + public string Identifier { get; } /// /// Gets the type of device this identifier belongs to diff --git a/src/Artemis.Core/Services/Input/InputProvider.cs b/src/Artemis.Core/Services/Input/InputProvider.cs index 8db4dec76..e5af94e48 100644 --- a/src/Artemis.Core/Services/Input/InputProvider.cs +++ b/src/Artemis.Core/Services/Input/InputProvider.cs @@ -113,7 +113,7 @@ protected virtual void OnMouseMoveDataReceived(ArtemisDevice? device, int cursor /// /// A value that can be used to identify this device /// The type of device this identifier belongs to - protected virtual void OnIdentifierReceived(object identifier, InputDeviceType deviceType) + protected virtual void OnIdentifierReceived(string identifier, InputDeviceType deviceType) { IdentifierReceived?.Invoke(this, new InputProviderIdentifierEventArgs(identifier, deviceType)); } diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index bbc4afe7a..932e3f507 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -145,19 +145,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable /// /// DeviceProvider GetDeviceProviderByDevice(IRGBDevice device); - - /// - /// Queues the provided plugin to be deleted the next time Artemis starts, before plugins are loaded - /// - /// The plugin to delete - void QueuePluginDeletion(Plugin plugin); - - /// - /// Removes the provided plugin for the deletion queue it was added to via - /// - /// The plugin to dequeue - void DequeuePluginDeletion(Plugin plugin); - + /// /// Occurs when built-in plugins are being loaded /// diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 67e3083e4..2f5d69638 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -30,22 +30,18 @@ internal class PluginManagementService : IPluginManagementService private readonly ILogger _logger; private readonly IPluginRepository _pluginRepository; private readonly List _plugins; - private readonly IQueuedActionRepository _queuedActionRepository; private FileSystemWatcher? _hotReloadWatcher; private bool _disposed; private bool _isElevated; - public PluginManagementService(IContainer container, ILogger logger, IPluginRepository pluginRepository, IDeviceRepository deviceRepository, IQueuedActionRepository queuedActionRepository) + public PluginManagementService(IContainer container, ILogger logger, IPluginRepository pluginRepository, IDeviceRepository deviceRepository) { _container = container; _logger = logger; _pluginRepository = pluginRepository; _deviceRepository = deviceRepository; - _queuedActionRepository = queuedActionRepository; _plugins = new List(); - - ProcessPluginDeletionQueue(); - + StartHotReload(); } @@ -800,59 +796,7 @@ public void DisablePluginFeature(PluginFeature pluginFeature, bool saveState) } #endregion - - #region Queued actions - - public void QueuePluginDeletion(Plugin plugin) - { - _queuedActionRepository.Add(new QueuedActionEntity - { - Type = "DeletePlugin", - CreatedAt = DateTimeOffset.Now, - Parameters = new Dictionary - { - {"pluginGuid", plugin.Guid.ToString()}, - {"plugin", plugin.ToString()}, - {"directory", plugin.Directory.FullName} - } - }); - } - - public void DequeuePluginDeletion(Plugin plugin) - { - QueuedActionEntity? queuedActionEntity = _queuedActionRepository.GetByType("DeletePlugin").FirstOrDefault(q => q.Parameters["pluginGuid"].Equals(plugin.Guid.ToString())); - if (queuedActionEntity != null) - _queuedActionRepository.Remove(queuedActionEntity); - } - - private void ProcessPluginDeletionQueue() - { - foreach (QueuedActionEntity queuedActionEntity in _queuedActionRepository.GetByType("DeletePlugin")) - { - string? directory = queuedActionEntity.Parameters["directory"].ToString(); - try - { - if (Directory.Exists(directory)) - { - _logger.Information("Queued plugin deletion - deleting folder - {plugin}", queuedActionEntity.Parameters["plugin"]); - Directory.Delete(directory!, true); - } - else - { - _logger.Information("Queued plugin deletion - folder already deleted - {plugin}", queuedActionEntity.Parameters["plugin"]); - } - - _queuedActionRepository.Remove(queuedActionEntity); - } - catch (Exception e) - { - _logger.Warning(e, "Queued plugin deletion failed - {plugin}", queuedActionEntity.Parameters["plugin"]); - } - } - } - - #endregion - + #region Storage private void SavePlugin(Plugin plugin) diff --git a/src/Artemis.Storage/Artemis.Storage.csproj b/src/Artemis.Storage/Artemis.Storage.csproj index b01c7ef62..83b31c461 100644 --- a/src/Artemis.Storage/Artemis.Storage.csproj +++ b/src/Artemis.Storage/Artemis.Storage.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Artemis.Storage/ArtemisDbContext.cs b/src/Artemis.Storage/ArtemisDbContext.cs new file mode 100644 index 000000000..a49717bd9 --- /dev/null +++ b/src/Artemis.Storage/ArtemisDbContext.cs @@ -0,0 +1,37 @@ +using Artemis.Storage.Entities.General; +using Artemis.Storage.Entities.Plugins; +using Artemis.Storage.Entities.Profile; +using Artemis.Storage.Entities.Surface; +using Artemis.Storage.Entities.Workshop; +using Microsoft.EntityFrameworkCore; + +namespace Artemis.Storage; + +public class ArtemisDbContext : DbContext +{ + public DbSet Devices => Set(); + public DbSet Entries => Set(); + public DbSet Plugins => Set(); + public DbSet PluginSettings => Set(); + public DbSet ProfileCategories => Set(); + public DbSet Profiles => Set(); + public DbSet Releases => Set(); + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .OwnsOne(d => d.InputIdentifiers, builder => builder.ToJson()) + .OwnsOne(d => d.InputMappings, builder => builder.ToJson()); + + modelBuilder.Entity() + .OwnsOne(e => e.Metadata, builder => builder.ToJson()); + + modelBuilder.Entity() + .OwnsOne(s => s.Value, builder => builder.ToJson()); + + modelBuilder.Entity() + .OwnsOne(c => c.ProfileConfiguration, builder => builder.ToJson()) + .OwnsOne(c => c.Profile, builder => builder.ToJson()); + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/General/QueuedActionEntity.cs b/src/Artemis.Storage/Entities/General/QueuedActionEntity.cs deleted file mode 100644 index 942a6501d..000000000 --- a/src/Artemis.Storage/Entities/General/QueuedActionEntity.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Artemis.Storage.Entities.General; - -public class QueuedActionEntity -{ - public QueuedActionEntity() - { - Parameters = new Dictionary(); - } - - public Guid Id { get; set; } - public string Type { get; set; } = string.Empty; - public DateTimeOffset CreatedAt { get; set; } - - public Dictionary Parameters { get; set; } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs index 1d604c28a..8be8cefb5 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json.Nodes; namespace Artemis.Storage.Entities.Plugins; @@ -11,5 +12,5 @@ public class PluginSettingEntity public Guid PluginGuid { get; set; } public string Name { get; set; } = string.Empty; - public string Value { get; set; } = string.Empty; + public JsonNode Value { get; set; } = new JsonObject(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs index 126845b38..7bdee9ecf 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs @@ -12,5 +12,12 @@ public class ProfileCategoryEntity public bool IsSuspended { get; set; } public int Order { get; set; } - public List ProfileConfigurations { get; set; } = new(); + public List ProfileConfigurations { get; set; } = new(); +} + +public class ProfileContainerEntity +{ + public byte[] Icon { get; set; } = Array.Empty(); + public ProfileConfigurationEntity ProfileConfiguration { get; set; } = new(); + public ProfileEntity Profile { get; set; } = new(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs index aa20adf7f..9157d460c 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs @@ -27,5 +27,5 @@ public class ProfileConfigurationEntity public Guid ProfileId { get; set; } public bool FadeInAndOut { get; set; } - public int Version { get; set; } = StorageMigrationService.PROFILE_VERSION; + public int Version { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs index 5075131c2..374a20941 100644 --- a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs +++ b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs @@ -42,5 +42,5 @@ public class InputMappingEntity public class DeviceInputIdentifierEntity { public string InputProvider { get; set; } = string.Empty; - public object Identifier { get; set; } = string.Empty; + public string Identifier { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs index 4be84d9e8..73a063003 100644 --- a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json.Nodes; namespace Artemis.Storage.Entities.Workshop; @@ -17,5 +18,5 @@ public class EntryEntity public string ReleaseVersion { get; set; } = string.Empty; public DateTimeOffset InstalledAt { get; set; } - public Dictionary? Metadata { get; set; } + public Dictionary? Metadata { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/IStorageMigration.cs b/src/Artemis.Storage/Migrations/IStorageMigration.cs deleted file mode 100644 index 4382a837d..000000000 --- a/src/Artemis.Storage/Migrations/IStorageMigration.cs +++ /dev/null @@ -1,9 +0,0 @@ -using LiteDB; - -namespace Artemis.Storage.Migrations; - -public interface IStorageMigration -{ - int UserVersion { get; } - void Apply(LiteRepository repository); -} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Storage/M0020AvaloniaReset.cs b/src/Artemis.Storage/Migrations/Storage/M0020AvaloniaReset.cs deleted file mode 100644 index 293fc1ff7..000000000 --- a/src/Artemis.Storage/Migrations/Storage/M0020AvaloniaReset.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using LiteDB; - -namespace Artemis.Storage.Migrations.Storage; - -public class M0020AvaloniaReset : IStorageMigration -{ - public int UserVersion => 20; - - public void Apply(LiteRepository repository) - { - repository.Database.Commit(); - - List collectionNames = repository.Database.GetCollectionNames().ToList(); - foreach (string collectionName in collectionNames) - repository.Database.DropCollection(collectionName); - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Storage/M0021GradientNodes.cs b/src/Artemis.Storage/Migrations/Storage/M0021GradientNodes.cs deleted file mode 100644 index 2bb90a45c..000000000 --- a/src/Artemis.Storage/Migrations/Storage/M0021GradientNodes.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Storage.Entities.Profile; -using Artemis.Storage.Entities.Profile.Nodes; -using LiteDB; - -namespace Artemis.Storage.Migrations.Storage; - -public class M0021GradientNodes : IStorageMigration -{ - private void MigrateDataBinding(PropertyEntity property) - { - NodeScriptEntity? script = property.DataBinding?.NodeScript; - NodeEntity? exitNode = script?.Nodes.FirstOrDefault(s => s.IsExitNode); - if (script == null || exitNode == null) - return; - - // Create a new node at the same position of the exit node - NodeEntity gradientNode = new() - { - Id = Guid.NewGuid(), - Type = "ColorGradientNode", - ProviderId = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78", - Name = "Color Gradient", - Description = "Outputs a color gradient with the given colors", - X = exitNode.X, - Y = exitNode.Y, - Storage = property.Value // Copy the value of the property into the node storage - }; - script.Nodes.Add(gradientNode); - - // Move all connections of the exit node to the new node - foreach (NodeConnectionEntity connection in script.Connections) - { - if (connection.SourceNode == exitNode.Id) - { - connection.SourceNode = gradientNode.Id; - connection.SourcePinId++; - } - } - - // Connect the data binding node to the source node - script.Connections.Add(new NodeConnectionEntity - { - SourceType = "ColorGradient", - SourceNode = exitNode.Id, - SourcePinCollectionId = -1, - SourcePinId = 0, - TargetType = "ColorGradient", - TargetNode = gradientNode.Id, - TargetPinCollectionId = -1, - TargetPinId = 0 - }); - - // Move the exit node to the right - exitNode.X += 300; - exitNode.Y += 30; - } - - private void MigrateDataBinding(PropertyGroupEntity? propertyGroup) - { - if (propertyGroup == null) - return; - - foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups) - MigrateDataBinding(propertyGroupPropertyGroup); - - foreach (PropertyEntity property in propertyGroup.Properties) - { - if (property.Value.StartsWith("[{\"Color\":\"") && property.DataBinding?.NodeScript != null && property.DataBinding.IsEnabled) - MigrateDataBinding(property); - } - } - - public int UserVersion => 21; - - public void Apply(LiteRepository repository) - { - // Find all color gradient data bindings, there's no really good way to do this so infer it from the value - List profiles = repository.Query().ToList(); - foreach (ProfileEntity profileEntity in profiles) - { - foreach (LayerEntity layer in profileEntity.Layers.Where(le => le.LayerBrush != null)) - MigrateDataBinding(layer.LayerBrush?.PropertyGroup); - - repository.Update(profileEntity); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Storage/M0022TransitionNodes.cs b/src/Artemis.Storage/Migrations/Storage/M0022TransitionNodes.cs deleted file mode 100644 index 791b95ea9..000000000 --- a/src/Artemis.Storage/Migrations/Storage/M0022TransitionNodes.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.Profile; -using Artemis.Storage.Entities.Profile.Abstract; -using Artemis.Storage.Entities.Profile.Conditions; -using Artemis.Storage.Entities.Profile.Nodes; -using LiteDB; - -namespace Artemis.Storage.Migrations.Storage; - -public class M0022TransitionNodes : IStorageMigration -{ - private void MigrateNodeScript(NodeScriptEntity? nodeScript) - { - if (nodeScript == null) - return; - - foreach (NodeEntity node in nodeScript.Nodes) - { - if (node.Type == "NumericEasingNode") - node.Type = "NumericTransitionNode"; - else if (node.Type == "ColorGradientEasingNode") - node.Type = "ColorGradientTransitionNode"; - else if (node.Type == "SKColorEasingNode") - node.Type = "SKColorTransitionNode"; - else if (node.Type == "EasingTypeNode") - node.Type = "EasingFunctionNode"; - } - } - - private void MigratePropertyGroup(PropertyGroupEntity? propertyGroup) - { - if (propertyGroup == null) - return; - - foreach (PropertyGroupEntity childPropertyGroup in propertyGroup.PropertyGroups) - MigratePropertyGroup(childPropertyGroup); - foreach (PropertyEntity property in propertyGroup.Properties) - MigrateNodeScript(property.DataBinding?.NodeScript); - } - - private void MigrateDisplayCondition(IConditionEntity? conditionEntity) - { - if (conditionEntity is EventConditionEntity eventConditionEntity) - MigrateNodeScript(eventConditionEntity.Script); - else if (conditionEntity is StaticConditionEntity staticConditionEntity) - MigrateNodeScript(staticConditionEntity.Script); - } - - public int UserVersion => 22; - - public void Apply(LiteRepository repository) - { - // Migrate profile configuration display conditions - List categories = repository.Query().ToList(); - foreach (ProfileCategoryEntity profileCategoryEntity in categories) - { - foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations) - MigrateNodeScript(profileConfigurationEntity.ActivationCondition); - repository.Update(profileCategoryEntity); - } - - // Migrate profile display conditions and data bindings - List profiles = repository.Query().ToList(); - foreach (ProfileEntity profileEntity in profiles) - { - foreach (LayerEntity layer in profileEntity.Layers) - { - MigratePropertyGroup(layer.LayerBrush?.PropertyGroup); - MigratePropertyGroup(layer.GeneralPropertyGroup); - MigratePropertyGroup(layer.TransformPropertyGroup); - foreach (LayerEffectEntity layerEffectEntity in layer.LayerEffects) - MigratePropertyGroup(layerEffectEntity.PropertyGroup); - MigrateDisplayCondition(layer.DisplayCondition); - } - - foreach (FolderEntity folder in profileEntity.Folders) - { - foreach (LayerEffectEntity folderLayerEffect in folder.LayerEffects) - MigratePropertyGroup(folderLayerEffect.PropertyGroup); - MigrateDisplayCondition(folder.DisplayCondition); - } - - repository.Update(profileEntity); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Storage/M0023LayoutProviders.cs b/src/Artemis.Storage/Migrations/Storage/M0023LayoutProviders.cs deleted file mode 100644 index 5296d5842..000000000 --- a/src/Artemis.Storage/Migrations/Storage/M0023LayoutProviders.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using LiteDB; - -namespace Artemis.Storage.Migrations.Storage; - -public class M0023LayoutProviders : IStorageMigration -{ - public int UserVersion => 23; - - public void Apply(LiteRepository repository) - { - ILiteCollection deviceEntities = repository.Database.GetCollection("DeviceEntity"); - List toUpdate = new(); - - foreach (BsonDocument bsonDocument in deviceEntities.FindAll()) - { - if (bsonDocument.TryGetValue("CustomLayoutPath", out BsonValue customLayoutPath) && customLayoutPath.IsString && !string.IsNullOrEmpty(customLayoutPath.AsString)) - { - bsonDocument.Add("LayoutType", new BsonValue("CustomPath")); - bsonDocument.Add("LayoutParameter", new BsonValue(customLayoutPath.AsString)); - } - else if (bsonDocument.TryGetValue("DisableDefaultLayout", out BsonValue disableDefaultLayout) && disableDefaultLayout.AsBoolean) - bsonDocument.Add("LayoutType", new BsonValue("None")); - else - bsonDocument.Add("LayoutType", new BsonValue("Default")); - - bsonDocument.Remove("CustomLayoutPath"); - bsonDocument.Remove("DisableDefaultLayout"); - toUpdate.Add(bsonDocument); - } - - deviceEntities.Update(toUpdate); - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Storage/M0024NodeProviders.cs b/src/Artemis.Storage/Migrations/Storage/M0024NodeProviders.cs deleted file mode 100644 index c09c0f1fc..000000000 --- a/src/Artemis.Storage/Migrations/Storage/M0024NodeProviders.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.Profile; -using LiteDB; - -namespace Artemis.Storage.Migrations.Storage; - -public class M0024NodeProviders : IStorageMigration -{ - public int UserVersion => 24; - - public void Apply(LiteRepository repository) - { - ILiteCollection categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity"); - List categoriesToUpdate = new(); - foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll()) - { - BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray; - if (profiles != null) - { - foreach (BsonValue profile in profiles) - profile["Version"] = 1; - categoriesToUpdate.Add(profileCategoryBson); - } - } - categoryCollection.Update(categoriesToUpdate); - - ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); - List profilesToUpdate = new(); - foreach (BsonDocument profileBson in collection.FindAll()) - { - BsonArray? folders = profileBson["Folders"]?.AsArray; - BsonArray? layers = profileBson["Layers"]?.AsArray; - - if (folders != null) - { - foreach (BsonValue folder in folders) - MigrateProfileElement(folder.AsDocument); - } - - if (layers != null) - { - foreach (BsonValue layer in layers) - { - MigrateProfileElement(layer.AsDocument); - MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument); - MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument); - MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument); - } - } - - profilesToUpdate.Add(profileBson); - } - - collection.Update(profilesToUpdate); - } - - private void MigrateProfileElement(BsonDocument profileElement) - { - BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray; - if (layerEffects != null) - { - foreach (BsonValue layerEffect in layerEffects) - MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument); - } - - BsonValue? displayCondition = profileElement["DisplayCondition"]; - if (displayCondition != null) - MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument); - } - - private void MigratePropertyGroup(BsonDocument? propertyGroup) - { - if (propertyGroup == null || propertyGroup.Keys.Count == 0) - return; - - BsonArray? properties = propertyGroup["Properties"]?.AsArray; - BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray; - - if (properties != null) - { - foreach (BsonValue property in properties) - MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument); - } - - if (propertyGroups != null) - { - foreach (BsonValue childPropertyGroup in propertyGroups) - MigratePropertyGroup(childPropertyGroup.AsDocument); - } - } - - private void MigrateNodeScript(BsonDocument? nodeScript) - { - if (nodeScript == null || nodeScript.Keys.Count == 0) - return; - - BsonArray? nodes = nodeScript["Nodes"]?.AsArray; - if (nodes == null) - return; - - foreach (BsonValue node in nodes) - { - node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes"); - node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78"; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Storage/M0025NodeProvidersProfileConfig.cs b/src/Artemis.Storage/Migrations/Storage/M0025NodeProvidersProfileConfig.cs deleted file mode 100644 index 46d8c3fe7..000000000 --- a/src/Artemis.Storage/Migrations/Storage/M0025NodeProvidersProfileConfig.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using LiteDB; - -namespace Artemis.Storage.Migrations.Storage; - -public class M0025NodeProvidersProfileConfig : IStorageMigration -{ - public int UserVersion => 25; - - public void Apply(LiteRepository repository) - { - ILiteCollection categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity"); - List toUpdate = new(); - foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll()) - { - BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray; - if (profiles != null) - { - foreach (BsonValue profile in profiles) - { - profile["Version"] = 2; - MigrateNodeScript(profile["ActivationCondition"]?.AsDocument); - } - toUpdate.Add(profileCategoryBson); - } - } - - categoryCollection.Update(toUpdate); - } - - private void MigrateNodeScript(BsonDocument? nodeScript) - { - if (nodeScript == null || nodeScript.Keys.Count == 0) - return; - - BsonArray? nodes = nodeScript["Nodes"]?.AsArray; - if (nodes == null) - return; - - foreach (BsonValue node in nodes) - { - node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes"); - node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78"; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/Storage/M0026NodeStorage.cs b/src/Artemis.Storage/Migrations/Storage/M0026NodeStorage.cs deleted file mode 100644 index f181f9698..000000000 --- a/src/Artemis.Storage/Migrations/Storage/M0026NodeStorage.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Migrations.Profile; -using LiteDB; -using Serilog; - -namespace Artemis.Storage.Migrations.Storage; - -public class M0026NodeStorage : IStorageMigration -{ - private readonly ILogger _logger; - - public M0026NodeStorage(ILogger logger) - { - _logger = logger; - } - public int UserVersion => 26; - - public void Apply(LiteRepository repository) - { - ILiteCollection categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity"); - List toUpdate = new(); - foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll()) - { - BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray; - if (profiles != null) - { - foreach (BsonValue profile in profiles) - { - profile["Version"] = 4; - MigrateNodeScript(profile["ActivationCondition"]?.AsDocument); - } - - toUpdate.Add(profileCategoryBson); - } - } - - categoryCollection.Update(toUpdate); - - ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); - List profilesToUpdate = new(); - foreach (BsonDocument profileBson in collection.FindAll()) - { - BsonArray? folders = profileBson["Folders"]?.AsArray; - BsonArray? layers = profileBson["Layers"]?.AsArray; - - if (folders != null) - { - foreach (BsonValue folder in folders) - MigrateProfileElement(folder.AsDocument); - } - - if (layers != null) - { - foreach (BsonValue layer in layers) - { - MigrateProfileElement(layer.AsDocument); - MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument); - MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument); - MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument); - } - } - - profilesToUpdate.Add(profileBson); - } - - collection.Update(profilesToUpdate); - } - - private void MigrateProfileElement(BsonDocument profileElement) - { - BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray; - if (layerEffects != null) - { - foreach (BsonValue layerEffect in layerEffects) - MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument); - } - - BsonValue? displayCondition = profileElement["DisplayCondition"]; - if (displayCondition != null) - MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument); - } - - private void MigratePropertyGroup(BsonDocument? propertyGroup) - { - if (propertyGroup == null || propertyGroup.Keys.Count == 0) - return; - - BsonArray? properties = propertyGroup["Properties"]?.AsArray; - BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray; - - if (properties != null) - { - foreach (BsonValue property in properties) - MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument); - } - - if (propertyGroups != null) - { - foreach (BsonValue childPropertyGroup in propertyGroups) - MigratePropertyGroup(childPropertyGroup.AsDocument); - } - } - - private void MigrateNodeScript(BsonDocument? nodeScript) - { - if (nodeScript == null || nodeScript.Keys.Count == 0) - return; - - BsonArray? nodes = nodeScript["Nodes"]?.AsArray; - if (nodes == null) - return; - - foreach (BsonValue node in nodes) - { - // Migrate the storage of the node - node["Storage"] = M0004NodeStorage.MigrateNodeStorageJson(node.AsDocument["Storage"]?.AsString, _logger); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/DeviceRepository.cs b/src/Artemis.Storage/Repositories/DeviceRepository.cs index 47a947333..d438da17a 100644 --- a/src/Artemis.Storage/Repositories/DeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/DeviceRepository.cs @@ -1,47 +1,43 @@ using System.Collections.Generic; +using System.Linq; using Artemis.Storage.Entities.Surface; using Artemis.Storage.Repositories.Interfaces; -using LiteDB; namespace Artemis.Storage.Repositories; internal class DeviceRepository : IDeviceRepository { - private readonly LiteRepository _repository; + private readonly ArtemisDbContext _dbContext; - public DeviceRepository(LiteRepository repository) + public DeviceRepository(ArtemisDbContext dbContext) { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Id); + _dbContext = dbContext; } public void Add(DeviceEntity deviceEntity) { - _repository.Insert(deviceEntity); + _dbContext.Devices.Add(deviceEntity); + SaveChanges(); } public void Remove(DeviceEntity deviceEntity) { - _repository.Delete(deviceEntity.Id); + _dbContext.Devices.Remove(deviceEntity); + SaveChanges(); } public DeviceEntity? Get(string id) { - return _repository.FirstOrDefault(s => s.Id == id); + return _dbContext.Devices.FirstOrDefault(d => d.Id == id); } public List GetAll() { - return _repository.Query().Include(s => s.InputIdentifiers).ToList(); + return _dbContext.Devices.ToList(); } - public void Save(DeviceEntity deviceEntity) + public void SaveChanges() { - _repository.Upsert(deviceEntity); - } - - public void Save(IEnumerable deviceEntities) - { - _repository.Upsert(deviceEntities); + _dbContext.SaveChanges(); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/EntryRepository.cs b/src/Artemis.Storage/Repositories/EntryRepository.cs index 81775f967..8f719eb52 100644 --- a/src/Artemis.Storage/Repositories/EntryRepository.cs +++ b/src/Artemis.Storage/Repositories/EntryRepository.cs @@ -1,54 +1,49 @@ using System; using System.Collections.Generic; +using System.Linq; using Artemis.Storage.Entities.Workshop; using Artemis.Storage.Repositories.Interfaces; -using LiteDB; namespace Artemis.Storage.Repositories; internal class EntryRepository : IEntryRepository { - private readonly LiteRepository _repository; + private readonly ArtemisDbContext _dbContext; - public EntryRepository(LiteRepository repository) + public EntryRepository(ArtemisDbContext dbContext) { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Id); - _repository.Database.GetCollection().EnsureIndex(s => s.EntryId); + _dbContext = dbContext; } public void Add(EntryEntity entryEntity) { - _repository.Insert(entryEntity); + _dbContext.Entries.Add(entryEntity); + SaveChanges(); } public void Remove(EntryEntity entryEntity) { - _repository.Delete(entryEntity.Id); + _dbContext.Entries.Remove(entryEntity); + SaveChanges(); } public EntryEntity? Get(Guid id) { - return _repository.FirstOrDefault(s => s.Id == id); + return _dbContext.Entries.FirstOrDefault(s => s.Id == id); } public EntryEntity? GetByEntryId(long entryId) { - return _repository.FirstOrDefault(s => s.EntryId == entryId); + return _dbContext.Entries.FirstOrDefault(s => s.EntryId == entryId); } public List GetAll() { - return _repository.Query().ToList(); + return _dbContext.Entries.ToList(); } - public void Save(EntryEntity entryEntity) + public void SaveChanges() { - _repository.Upsert(entryEntity); - } - - public void Save(IEnumerable entryEntities) - { - _repository.Upsert(entryEntities); + _dbContext.SaveChanges(); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs index 50fd1df86..172f23e61 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs @@ -9,6 +9,5 @@ public interface IDeviceRepository : IRepository void Remove(DeviceEntity deviceEntity); DeviceEntity? Get(string id); List GetAll(); - void Save(DeviceEntity deviceEntity); - void Save(IEnumerable deviceEntities); + void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs index 59d610f95..0e16c6dde 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs @@ -11,6 +11,5 @@ public interface IEntryRepository : IRepository EntryEntity? Get(Guid id); EntryEntity? GetByEntryId(long entryId); List GetAll(); - void Save(EntryEntity entryEntity); - void Save(IEnumerable entryEntities); + void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs index c180ee33f..d6d766344 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs @@ -7,11 +7,9 @@ public interface IPluginRepository : IRepository { void AddPlugin(PluginEntity pluginEntity); PluginEntity? GetPluginByGuid(Guid pluginGuid); - void SavePlugin(PluginEntity pluginEntity); - void AddSetting(PluginSettingEntity pluginSettingEntity); PluginSettingEntity? GetSettingByGuid(Guid pluginGuid); PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid); - void SaveSetting(PluginSettingEntity pluginSettingEntity); void RemoveSettings(Guid pluginGuid); + void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs index 878040e2c..cf3dccee6 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs @@ -11,8 +11,6 @@ public interface IProfileCategoryRepository : IRepository void Remove(ProfileCategoryEntity profileCategoryEntity); List GetAll(); ProfileCategoryEntity? Get(Guid id); - Stream? GetProfileIconStream(Guid id); - void SaveProfileIconStream(ProfileConfigurationEntity profileConfigurationEntity, Stream stream); - ProfileCategoryEntity IsUnique(string name, Guid? id); - void Save(ProfileCategoryEntity profileCategoryEntity); + bool IsUnique(string name, Guid? id); + void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs index 8430b82c4..5133c0328 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs @@ -6,9 +6,9 @@ namespace Artemis.Storage.Repositories.Interfaces; public interface IProfileRepository : IRepository { - void Add(ProfileEntity profileEntity); - void Remove(ProfileEntity profileEntity); - List GetAll(); - ProfileEntity? Get(Guid id); - void Save(ProfileEntity profileEntity); + void Add(ProfileContainerEntity profileContainerEntity); + void Remove(ProfileContainerEntity profileContainerEntity); + List GetAll(); + ProfileContainerEntity? Get(Guid id); + void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IQueuedActionRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IQueuedActionRepository.cs deleted file mode 100644 index cb5852eaa..000000000 --- a/src/Artemis.Storage/Repositories/Interfaces/IQueuedActionRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.General; - -namespace Artemis.Storage.Repositories.Interfaces; - -public interface IQueuedActionRepository : IRepository -{ - void Add(QueuedActionEntity queuedActionEntity); - void Remove(QueuedActionEntity queuedActionEntity); - List GetAll(); - List GetByType(string type); - bool IsTypeQueued(string type); - void ClearByType(string type); -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs index 02a8985c0..190530bbc 100644 --- a/src/Artemis.Storage/Repositories/PluginRepository.cs +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -1,59 +1,53 @@ using System; +using System.Linq; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; -using LiteDB; namespace Artemis.Storage.Repositories; internal class PluginRepository : IPluginRepository { - private readonly LiteRepository _repository; + private readonly ArtemisDbContext _dbContext; - public PluginRepository(LiteRepository repository) + public PluginRepository(ArtemisDbContext dbContext) { - _repository = repository; - - _repository.Database.GetCollection().EnsureIndex(s => new {s.Name, s.PluginGuid}, true); + _dbContext = dbContext; } public void AddPlugin(PluginEntity pluginEntity) { - _repository.Insert(pluginEntity); + _dbContext.Plugins.Add(pluginEntity); + SaveChanges(); } public PluginEntity? GetPluginByGuid(Guid pluginGuid) { - return _repository.FirstOrDefault(p => p.Id == pluginGuid); + return _dbContext.Plugins.FirstOrDefault(p => p.Id == pluginGuid); } - - public void SavePlugin(PluginEntity pluginEntity) - { - _repository.Upsert(pluginEntity); - } - + public void AddSetting(PluginSettingEntity pluginSettingEntity) { - _repository.Insert(pluginSettingEntity); + _dbContext.PluginSettings.Add(pluginSettingEntity); + SaveChanges(); } public PluginSettingEntity? GetSettingByGuid(Guid pluginGuid) { - return _repository.FirstOrDefault(p => p.PluginGuid == pluginGuid); + return _dbContext.PluginSettings.FirstOrDefault(p => p.PluginGuid == pluginGuid); } public PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid) { - return _repository.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid); + return _dbContext.PluginSettings.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid); } - - public void SaveSetting(PluginSettingEntity pluginSettingEntity) + + public void RemoveSettings(Guid pluginGuid) { - _repository.Upsert(pluginSettingEntity); + _dbContext.PluginSettings.RemoveRange(_dbContext.PluginSettings.Where(s => s.PluginGuid == pluginGuid)); } - - /// - public void RemoveSettings(Guid pluginGuid) + + public void SaveChanges() { - _repository.DeleteMany(s => s.PluginGuid == pluginGuid); + _dbContext.SaveChanges(); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs index 8036b9d7a..7d0ef11bc 100644 --- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs @@ -1,93 +1,53 @@ using System; using System.Collections.Generic; -using System.IO; +using System.Linq; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Repositories.Interfaces; -using LiteDB; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Repositories; internal class ProfileCategoryRepository : IProfileCategoryRepository { - private readonly ILiteStorage _profileIcons; - private readonly LiteRepository _repository; + private readonly ArtemisDbContext _dbContext; - public ProfileCategoryRepository(LiteRepository repository) + public ProfileCategoryRepository(ArtemisDbContext dbContext) { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Name, true); - _profileIcons = _repository.Database.GetStorage("profileIcons"); + _dbContext = dbContext; } public void Add(ProfileCategoryEntity profileCategoryEntity) { - _repository.Insert(profileCategoryEntity); + _dbContext.ProfileCategories.Add(profileCategoryEntity); + SaveChanges(); } public void Remove(ProfileCategoryEntity profileCategoryEntity) { - _repository.Delete(profileCategoryEntity.Id); + _dbContext.ProfileCategories.Remove(profileCategoryEntity); + SaveChanges(); } public List GetAll() { - List categories = _repository.Query().ToList(); - - // Update all profile versions to the current version, profile migrations don't apply to LiteDB so anything loadable is assumed to be up to date - foreach (ProfileCategoryEntity profileCategoryEntity in categories) - UpdateProfileVersions(profileCategoryEntity); - - return categories; + return _dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).ToList(); } public ProfileCategoryEntity? Get(Guid id) { - ProfileCategoryEntity? result = _repository.FirstOrDefault(p => p.Id == id); - if (result == null) - return null; - - // Update all profile versions to the current version, profile migrations don't apply to LiteDB so anything loadable is assumed to be up to date - UpdateProfileVersions(result); - return result; + return _dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).FirstOrDefault(c => c.Id == id); } - public ProfileCategoryEntity IsUnique(string name, Guid? id) + public bool IsUnique(string name, Guid? id) { name = name.Trim(); if (id == null) - return _repository.FirstOrDefault(p => p.Name == name); - return _repository.FirstOrDefault(p => p.Name == name && p.Id != id.Value); - } - - public void Save(ProfileCategoryEntity profileCategoryEntity) - { - _repository.Upsert(profileCategoryEntity); - } - - public Stream? GetProfileIconStream(Guid id) - { - if (!_profileIcons.Exists(id)) - return null; - - MemoryStream stream = new(); - _profileIcons.Download(id, stream); - return stream; - } - - public void SaveProfileIconStream(ProfileConfigurationEntity profileConfigurationEntity, Stream stream) - { - if (profileConfigurationEntity.FileIconId == Guid.Empty) - profileConfigurationEntity.FileIconId = Guid.NewGuid(); - - if (stream == null && _profileIcons.Exists(profileConfigurationEntity.FileIconId)) - _profileIcons.Delete(profileConfigurationEntity.FileIconId); - - _profileIcons.Upload(profileConfigurationEntity.FileIconId, profileConfigurationEntity.FileIconId + ".png", stream); + return _dbContext.ProfileCategories.Any(p => p.Name == name); + return _dbContext.ProfileCategories.Any(p => p.Name == name && p.Id != id.Value); } - private static void UpdateProfileVersions(ProfileCategoryEntity profileCategoryEntity) + public void SaveChanges() { - foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations) - profileConfigurationEntity.Version = StorageMigrationService.PROFILE_VERSION; + _dbContext.SaveChanges(); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs index 738099a32..0a2c1b88b 100644 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Artemis.Storage.Entities.Profile; using Artemis.Storage.Repositories.Interfaces; using LiteDB; @@ -8,36 +9,37 @@ namespace Artemis.Storage.Repositories; internal class ProfileRepository : IProfileRepository { - private readonly LiteRepository _repository; + private readonly ArtemisDbContext _dbContext; - public ProfileRepository(LiteRepository repository) + public ProfileRepository(ArtemisDbContext dbContext) { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Name); + _dbContext = dbContext; } - public void Add(ProfileEntity profileEntity) + public void Add(ProfileContainerEntity profileContainerEntity) { - _repository.Insert(profileEntity); + _dbContext.Profiles.Add(profileContainerEntity); + SaveChanges(); } - public void Remove(ProfileEntity profileEntity) + public void Remove(ProfileContainerEntity profileContainerEntity) { - _repository.Delete(profileEntity.Id); + _dbContext.Profiles.Remove(profileContainerEntity); + SaveChanges(); } - public List GetAll() + public List GetAll() { - return _repository.Query().ToList(); + return _dbContext.Profiles.ToList(); } - public ProfileEntity? Get(Guid id) + public ProfileContainerEntity? Get(Guid id) { - return _repository.FirstOrDefault(p => p.Id == id); + return _dbContext.Profiles.FirstOrDefault(c => c.Profile.Id == id); } - public void Save(ProfileEntity profileEntity) + public void SaveChanges() { - _repository.Upsert(profileEntity); + _dbContext.SaveChanges(); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/QueuedActionRepository.cs b/src/Artemis.Storage/Repositories/QueuedActionRepository.cs deleted file mode 100644 index cf2bc862d..000000000 --- a/src/Artemis.Storage/Repositories/QueuedActionRepository.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using Artemis.Storage.Entities.General; -using Artemis.Storage.Repositories.Interfaces; -using LiteDB; - -namespace Artemis.Storage.Repositories; - -public class QueuedActionRepository : IQueuedActionRepository -{ - private readonly LiteRepository _repository; - - public QueuedActionRepository(LiteRepository repository) - { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Type); - } - - #region Implementation of IQueuedActionRepository - - /// - public void Add(QueuedActionEntity queuedActionEntity) - { - _repository.Insert(queuedActionEntity); - } - - /// - public void Remove(QueuedActionEntity queuedActionEntity) - { - _repository.Delete(queuedActionEntity.Id); - } - - /// - public List GetAll() - { - return _repository.Query().ToList(); - } - - /// - public List GetByType(string type) - { - return _repository.Query().Where(q => q.Type == type).ToList(); - } - - /// - public bool IsTypeQueued(string type) - { - return _repository.Query().Where(q => q.Type == type).Count() > 0; - } - - /// - public void ClearByType(string type) - { - _repository.DeleteMany(q => q.Type == type); - } - - #endregion -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ReleaseRepository.cs b/src/Artemis.Storage/Repositories/ReleaseRepository.cs index c5c18ae27..d7e320919 100644 --- a/src/Artemis.Storage/Repositories/ReleaseRepository.cs +++ b/src/Artemis.Storage/Repositories/ReleaseRepository.cs @@ -1,38 +1,38 @@ using System; +using System.Linq; using Artemis.Storage.Entities.General; using Artemis.Storage.Repositories.Interfaces; -using LiteDB; namespace Artemis.Storage.Repositories; public class ReleaseRepository : IReleaseRepository { - private readonly LiteRepository _repository; + private readonly ArtemisDbContext _dbContext; - public ReleaseRepository(LiteRepository repository) + public ReleaseRepository(ArtemisDbContext dbContext) { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.Version, true); + _dbContext = dbContext; } public bool SaveVersionInstallDate(string version) { - ReleaseEntity release = _repository.Query().Where(r => r.Version == version).FirstOrDefault(); + ReleaseEntity? release = _dbContext.Releases.FirstOrDefault(r => r.Version == version); if (release != null) return false; - _repository.Insert(new ReleaseEntity {Version = version, InstalledAt = DateTimeOffset.UtcNow}); + _dbContext.Releases.Add(new ReleaseEntity {Version = version, InstalledAt = DateTimeOffset.UtcNow}); + _dbContext.SaveChanges(); return true; } - public ReleaseEntity GetPreviousInstalledVersion() + public ReleaseEntity? GetPreviousInstalledVersion() { - return _repository.Query().OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault(); + return _dbContext.Releases.OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault(); } } public interface IReleaseRepository : IRepository { bool SaveVersionInstallDate(string version); - ReleaseEntity GetPreviousInstalledVersion(); + ReleaseEntity? GetPreviousInstalledVersion(); } \ No newline at end of file diff --git a/src/Artemis.Storage/StorageMigrationService.cs b/src/Artemis.Storage/StorageMigrationService.cs deleted file mode 100644 index eca2a7dde..000000000 --- a/src/Artemis.Storage/StorageMigrationService.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Storage.Migrations; -using LiteDB; -using Serilog; - -namespace Artemis.Storage; - -public class StorageMigrationService -{ - public const int PROFILE_VERSION = 4; - - private readonly ILogger _logger; - private readonly IList _migrations; - private readonly LiteRepository _repository; - - public StorageMigrationService(ILogger logger, LiteRepository repository, IList migrations) - { - _logger = logger; - _repository = repository; - _migrations = migrations; - - ApplyPendingMigrations(); - } - - public void ApplyPendingMigrations() - { - foreach (IStorageMigration storageMigration in _migrations.OrderBy(m => m.UserVersion)) - { - if (_repository.Database.UserVersion >= storageMigration.UserVersion) - continue; - - _logger.Information("Applying storage migration {storageMigration} to update DB from v{oldVersion} to v{newVersion}", - storageMigration.GetType().Name, _repository.Database.UserVersion, storageMigration.UserVersion); - - _repository.Database.BeginTrans(); - try - { - storageMigration.Apply(_repository); - } - catch (Exception) - { - _repository.Database.Rollback(); - throw; - } - - _repository.Database.Commit(); - - _repository.Database.UserVersion = storageMigration.UserVersion; - } - } -} \ No newline at end of file diff --git a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs index 90aa4108e..1f722cc5d 100644 --- a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs +++ b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs @@ -1,12 +1,16 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Nodes; using Artemis.Core; using Artemis.Storage.Entities.Workshop; +using Artemis.WebClient.Workshop.Exceptions; namespace Artemis.WebClient.Workshop.Models; public class InstalledEntry { - private Dictionary _metadata = new(); + private static readonly JsonSerializerOptions JsonSerializerOptions = CoreJson.GetJsonSerializerOptions(); + private Dictionary _metadata = new(); internal InstalledEntry(EntryEntity entity) { @@ -52,7 +56,7 @@ internal void Load() ReleaseVersion = Entity.ReleaseVersion; InstalledAt = Entity.InstalledAt; - _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); + _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); } internal void Save() @@ -67,7 +71,7 @@ internal void Save() Entity.ReleaseVersion = ReleaseVersion; Entity.InstalledAt = InstalledAt; - Entity.Metadata = new Dictionary(_metadata); + Entity.Metadata = new Dictionary(_metadata); } /// @@ -80,14 +84,29 @@ internal void Save() /// if the metadata contains an element with the specified key; otherwise, . public bool TryGetMetadata(string key, [NotNullWhen(true)] out T? value) { - if (!_metadata.TryGetValue(key, out object? objectValue) || objectValue is not T result) + if (!_metadata.TryGetValue(key, out JsonNode? jsonNode)) { value = default; return false; } - value = result; - return true; + try + { + T? deserialized = jsonNode.Deserialize(JsonSerializerOptions); + if (deserialized != null) + { + value = deserialized; + return true; + } + + value = default; + return false; + } + catch (Exception) + { + value = default; + return false; + } } /// @@ -97,7 +116,8 @@ public bool TryGetMetadata(string key, [NotNullWhen(true)] out T? value) /// The value to set. public void SetMetadata(string key, object value) { - _metadata[key] = value; + JsonNode? jsonNode = JsonSerializer.SerializeToNode(value, JsonSerializerOptions); + _metadata[key] = jsonNode ?? throw new ArtemisWorkshopException("Failed to serialize metadata value"); } /// diff --git a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs index 95b90ccc5..6d2d36e9a 100644 --- a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs @@ -171,7 +171,7 @@ public void RemoveInstalledEntry(InstalledEntry installedEntry) public void SaveInstalledEntry(InstalledEntry entry) { entry.Save(); - _entryRepository.Save(entry.Entity); + _entryRepository.SaveChanges(); } /// diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 214bbcd9c..f1830bf8a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -29,6 +29,8 @@ + + From 41a754377848dbad370a8f60af96cc1ae3fb0632 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Fri, 8 Mar 2024 21:44:28 +0100 Subject: [PATCH 02/14] Storage - Got it to build and even run --- .../DryIoc/ContainerExtensions.cs | 3 +- .../Models/Profile/ProfileCategory.cs | 9 +- .../ProfileConfiguration.cs | 54 +-- .../ProfileConfigurationIcon.cs | 65 ++-- .../Plugins/Settings/IPluginSetting.cs | 8 +- .../Plugins/Settings/PluginSetting.cs | 9 +- src/Artemis.Core/Services/CoreService.cs | 1 - src/Artemis.Core/Services/DeviceService.cs | 10 +- .../Services/PluginManagementService.cs | 2 +- src/Artemis.Core/Services/ScriptingService.cs | 22 +- .../Storage/Interfaces/IProfileService.cs | 15 - .../Services/Storage/ProfileService.cs | 200 ++++------- .../Artemis.Storage.Migrator.csproj | 22 ++ src/Artemis.Storage.Migrator/Program.cs | 21 ++ src/Artemis.Storage.Migrator/artemis.db | Bin 0 -> 86016 bytes src/Artemis.Storage/ArtemisDbContext.cs | 36 +- .../Entities/Plugins/PluginEntity.cs | 2 + .../Entities/Plugins/PluginSettingEntity.cs | 3 +- .../Entities/Profile/ProfileCategoryEntity.cs | 7 - .../Profile/ProfileContainerEntity.cs | 14 + .../Entities/Workshop/EntryEntity.cs | 2 +- .../20240308203921_Initial.Designer.cs | 323 ++++++++++++++++++ .../Migrations/20240308203921_Initial.cs | 194 +++++++++++ .../ArtemisDbContextModelSnapshot.cs | 320 +++++++++++++++++ .../Interfaces/IProfileRepository.cs | 14 - .../Repositories/ProfileRepository.cs | 45 --- src/Artemis.Storage/StorageManager.cs | 8 + .../ProfileConfigurationIcon.axaml.cs | 13 +- .../ProfileEditor/ProfileEditorViewModel.cs | 2 +- .../ProfileConfigurationEditViewModel.cs | 3 +- .../SidebarProfileConfigurationViewModel.cs | 9 - .../Profile/ProfileSelectionStepViewModel.cs | 8 +- .../ProfileEntryInstallationHandler.cs | 2 +- .../Models/InstalledEntry.cs | 34 +- src/Artemis.sln | 6 + src/Directory.Packages.props | 1 + 36 files changed, 1109 insertions(+), 378 deletions(-) create mode 100644 src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj create mode 100644 src/Artemis.Storage.Migrator/Program.cs create mode 100644 src/Artemis.Storage.Migrator/artemis.db create mode 100644 src/Artemis.Storage/Entities/Profile/ProfileContainerEntity.cs create mode 100644 src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs create mode 100644 src/Artemis.Storage/Migrations/20240308203921_Initial.cs create mode 100644 src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs delete mode 100644 src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs delete mode 100644 src/Artemis.Storage/Repositories/ProfileRepository.cs diff --git a/src/Artemis.Core/DryIoc/ContainerExtensions.cs b/src/Artemis.Core/DryIoc/ContainerExtensions.cs index 5e057e049..ba0ede352 100644 --- a/src/Artemis.Core/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.Core/DryIoc/ContainerExtensions.cs @@ -31,8 +31,9 @@ public static void RegisterCore(this IContainer container) // Bind storage container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton); + container.RegisterDelegate(() => StorageManager.CreateDbContext(Constants.DataFolder), Reuse.Transient); container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton); - + // Bind migrations container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton, nonPublicServiceTypes: true); diff --git a/src/Artemis.Core/Models/Profile/ProfileCategory.cs b/src/Artemis.Core/Models/Profile/ProfileCategory.cs index 4dc3f73e6..63dbafffa 100644 --- a/src/Artemis.Core/Models/Profile/ProfileCategory.cs +++ b/src/Artemis.Core/Models/Profile/ProfileCategory.cs @@ -175,7 +175,7 @@ public void Load() Order = Entity.Order; _profileConfigurations.Clear(); - foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations) + foreach (ProfileContainerEntity entityProfileConfiguration in Entity.ProfileConfigurations) _profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration)); } @@ -186,13 +186,10 @@ public void Save() Entity.IsCollapsed = IsCollapsed; Entity.IsSuspended = IsSuspended; Entity.Order = Order; - + Entity.ProfileConfigurations.Clear(); - foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) - { - profileConfiguration.Save(); + foreach (ProfileConfiguration profileConfiguration in _profileConfigurations) Entity.ProfileConfigurations.Add(profileConfiguration.Entity); - } } #endregion diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index a4c1e89f4..521561610 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -37,13 +37,13 @@ internal ProfileConfiguration(ProfileCategory category, string name, string icon _name = name; _category = category; - Entity = new ProfileConfigurationEntity(); + Entity = new ProfileContainerEntity(); Icon = new ProfileConfigurationIcon(Entity); Icon.SetIconByName(icon); ActivationCondition = new NodeScript("Activate profile", "Whether or not the profile should be active", this); } - internal ProfileConfiguration(ProfileCategory category, ProfileConfigurationEntity entity) + internal ProfileConfiguration(ProfileCategory category, ProfileContainerEntity entity) { // Will be loaded from the entity _name = null!; @@ -192,12 +192,12 @@ public Module? Module /// /// Gets the entity used by this profile config /// - public ProfileConfigurationEntity Entity { get; } + public ProfileContainerEntity Entity { get; } /// /// Gets the ID of the profile of this profile configuration /// - public Guid ProfileId => Entity.ProfileId; + public Guid ProfileId => Entity.Profile.Id; #region Overrides of BreakableModel @@ -265,8 +265,8 @@ internal void LoadModules(List enabledModules) if (_disposed) throw new ObjectDisposedException("ProfileConfiguration"); - Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ModuleId); - IsMissingModule = Module == null && Entity.ModuleId != null; + Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ProfileConfiguration.ModuleId); + IsMissingModule = Module == null && Entity.ProfileConfiguration.ModuleId != null; } /// @@ -284,20 +284,20 @@ public void Load() if (_disposed) throw new ObjectDisposedException("ProfileConfiguration"); - Name = Entity.Name; - IsSuspended = Entity.IsSuspended; - ActivationBehaviour = (ActivationBehaviour) Entity.ActivationBehaviour; - HotkeyMode = (ProfileConfigurationHotkeyMode) Entity.HotkeyMode; - FadeInAndOut = Entity.FadeInAndOut; - Order = Entity.Order; + Name = Entity.ProfileConfiguration.Name; + IsSuspended = Entity.ProfileConfiguration.IsSuspended; + ActivationBehaviour = (ActivationBehaviour) Entity.ProfileConfiguration.ActivationBehaviour; + HotkeyMode = (ProfileConfigurationHotkeyMode) Entity.ProfileConfiguration.HotkeyMode; + FadeInAndOut = Entity.ProfileConfiguration.FadeInAndOut; + Order = Entity.ProfileConfiguration.Order; Icon.Load(); - if (Entity.ActivationCondition != null) - ActivationCondition.LoadFromEntity(Entity.ActivationCondition); + if (Entity.ProfileConfiguration.ActivationCondition != null) + ActivationCondition.LoadFromEntity(Entity.ProfileConfiguration.ActivationCondition); - EnableHotkey = Entity.EnableHotkey != null ? new Hotkey(Entity.EnableHotkey) : null; - DisableHotkey = Entity.DisableHotkey != null ? new Hotkey(Entity.DisableHotkey) : null; + EnableHotkey = Entity.ProfileConfiguration.EnableHotkey != null ? new Hotkey(Entity.ProfileConfiguration.EnableHotkey) : null; + DisableHotkey = Entity.ProfileConfiguration.DisableHotkey != null ? new Hotkey(Entity.ProfileConfiguration.DisableHotkey) : null; } /// @@ -306,26 +306,26 @@ public void Save() if (_disposed) throw new ObjectDisposedException("ProfileConfiguration"); - Entity.Name = Name; - Entity.IsSuspended = IsSuspended; - Entity.ActivationBehaviour = (int) ActivationBehaviour; - Entity.HotkeyMode = (int) HotkeyMode; - Entity.ProfileCategoryId = Category.Entity.Id; - Entity.FadeInAndOut = FadeInAndOut; - Entity.Order = Order; + Entity.ProfileConfiguration.Name = Name; + Entity.ProfileConfiguration.IsSuspended = IsSuspended; + Entity.ProfileConfiguration.ActivationBehaviour = (int) ActivationBehaviour; + Entity.ProfileConfiguration.HotkeyMode = (int) HotkeyMode; + Entity.ProfileConfiguration.ProfileCategoryId = Category.Entity.Id; + Entity.ProfileConfiguration.FadeInAndOut = FadeInAndOut; + Entity.ProfileConfiguration.Order = Order; Icon.Save(); ActivationCondition.Save(); - Entity.ActivationCondition = ActivationCondition.Entity; + Entity.ProfileConfiguration.ActivationCondition = ActivationCondition.Entity; EnableHotkey?.Save(); - Entity.EnableHotkey = EnableHotkey?.Entity; + Entity.ProfileConfiguration.EnableHotkey = EnableHotkey?.Entity; DisableHotkey?.Save(); - Entity.DisableHotkey = DisableHotkey?.Entity; + Entity.ProfileConfiguration.DisableHotkey = DisableHotkey?.Entity; if (!IsMissingModule) - Entity.ModuleId = Module?.Id; + Entity.ProfileConfiguration.ModuleId = Module?.Id; } #endregion diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs index 5a477b5fd..f2916190e 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs @@ -10,13 +10,13 @@ namespace Artemis.Core; /// public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel { - private readonly ProfileConfigurationEntity _entity; + private readonly ProfileContainerEntity _entity; private bool _fill; private string? _iconName; - private Stream? _iconStream; + private byte[]? _iconBytes; private ProfileConfigurationIconType _iconType; - internal ProfileConfigurationIcon(ProfileConfigurationEntity entity) + internal ProfileConfigurationIcon(ProfileContainerEntity entity) { _entity = entity; } @@ -48,6 +48,15 @@ public bool Fill set => SetAndNotify(ref _fill, value); } + /// + /// Gets or sets the icon bytes if is + /// + public byte[]? IconBytes + { + get => _iconBytes; + private set => SetAndNotify(ref _iconBytes, value); + } + /// /// Updates the to the provided value and changes the is /// @@ -55,9 +64,9 @@ public bool Fill /// The name of the icon public void SetIconByName(string iconName) { - if (iconName == null) throw new ArgumentNullException(nameof(iconName)); + ArgumentNullException.ThrowIfNull(iconName); - _iconStream?.Dispose(); + IconBytes = null; IconName = iconName; IconType = ProfileConfigurationIconType.MaterialIcon; @@ -65,42 +74,27 @@ public void SetIconByName(string iconName) } /// - /// Updates the stream returned by to the provided stream + /// Updates the to the provided value and changes the is /// /// The stream to copy public void SetIconByStream(Stream stream) { - if (stream == null) throw new ArgumentNullException(nameof(stream)); + ArgumentNullException.ThrowIfNull(stream); - _iconStream?.Dispose(); - _iconStream = new MemoryStream(); if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); - stream.CopyTo(_iconStream); - _iconStream.Seek(0, SeekOrigin.Begin); + + using (MemoryStream ms = new()) + { + stream.CopyTo(ms); + IconBytes = ms.ToArray(); + } IconName = null; IconType = ProfileConfigurationIconType.BitmapImage; OnIconUpdated(); } - /// - /// Creates a copy of the stream containing the icon - /// - /// A stream containing the icon - public Stream? GetIconStream() - { - if (_iconStream == null) - return null; - - MemoryStream stream = new(); - _iconStream.CopyTo(stream); - - stream.Seek(0, SeekOrigin.Begin); - _iconStream.Seek(0, SeekOrigin.Begin); - return stream; - } - /// /// Occurs when the icon was updated /// @@ -119,21 +113,24 @@ protected virtual void OnIconUpdated() /// public void Load() { - IconType = (ProfileConfigurationIconType) _entity.IconType; - Fill = _entity.IconFill; + IconType = (ProfileConfigurationIconType) _entity.ProfileConfiguration.IconType; + Fill = _entity.ProfileConfiguration.IconFill; if (IconType != ProfileConfigurationIconType.MaterialIcon) return; - IconName = _entity.MaterialIcon; + IconName = _entity.ProfileConfiguration.MaterialIcon; + IconBytes = IconType == ProfileConfigurationIconType.BitmapImage ? _entity.Icon : null; + OnIconUpdated(); } /// public void Save() { - _entity.IconType = (int) IconType; - _entity.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null; - _entity.IconFill = Fill; + _entity.ProfileConfiguration.IconType = (int) IconType; + _entity.ProfileConfiguration.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null; + _entity.ProfileConfiguration.IconFill = Fill; + _entity.Icon = IconBytes ?? Array.Empty(); } #endregion diff --git a/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs b/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs index a33844c1f..e27282063 100644 --- a/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs @@ -19,13 +19,7 @@ public interface IPluginSetting string Name { get; } /// - /// Determines whether the setting has been changed - /// - bool HasChanged { get; } - - /// - /// Gets or sets whether changes must automatically be saved - /// Note: When set to true is always false + /// Gets or sets whether changes must automatically be saved /// bool AutoSave { get; set; } diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs index 390566c76..e3510a58e 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs @@ -1,6 +1,5 @@ using System; using System.Text.Json; -using System.Text.Json.Nodes; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; @@ -24,7 +23,7 @@ internal PluginSetting(IPluginRepository pluginRepository, PluginSettingEntity p Name = pluginSettingEntity.Name; try { - _value = pluginSettingEntity.Value.Deserialize(IPluginSetting.SerializerOptions) ?? default!; + _value = CoreJson.Deserialize(pluginSettingEntity.Value)!; } catch (JsonException) { @@ -77,7 +76,7 @@ protected internal virtual void OnSettingSaved() public string Name { get; } /// - public bool HasChanged => !JsonNode.DeepEquals(JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions), _pluginSettingEntity.Value); + public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value; /// public bool AutoSave { get; set; } @@ -85,7 +84,7 @@ protected internal virtual void OnSettingSaved() /// public void RejectChanges() { - Value = _pluginSettingEntity.Value.Deserialize(IPluginSetting.SerializerOptions) ?? default!; + Value = CoreJson.Deserialize(_pluginSettingEntity.Value); } /// @@ -94,7 +93,7 @@ public void Save() if (!HasChanged) return; - _pluginSettingEntity.Value = JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions) ?? new JsonObject(); + _pluginSettingEntity.Value = CoreJson.Serialize(Value); _pluginRepository.SaveChanges(); OnSettingSaved(); } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 89a951149..b27a8601b 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -29,7 +29,6 @@ internal class CoreService : ICoreService // ReSharper disable UnusedParameter.Local public CoreService(IContainer container, ILogger logger, - StorageMigrationService _1, // injected to ensure migration runs early ISettingsService settingsService, IPluginManagementService pluginManagementService, IProfileService profileService, diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index afc949bba..d110c90a4 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -202,7 +202,7 @@ public void EnableDevice(ArtemisDevice device) _enabledDevices.Add(device); device.IsEnabled = true; device.Save(); - _deviceRepository.Save(device.DeviceEntity); + _deviceRepository.SaveChanges(); OnDeviceEnabled(new DeviceEventArgs(device)); UpdateLeds(); @@ -217,7 +217,7 @@ public void DisableDevice(ArtemisDevice device) _enabledDevices.Remove(device); device.IsEnabled = false; device.Save(); - _deviceRepository.Save(device.DeviceEntity); + _deviceRepository.SaveChanges(); OnDeviceDisabled(new DeviceEventArgs(device)); UpdateLeds(); @@ -227,7 +227,7 @@ public void DisableDevice(ArtemisDevice device) public void SaveDevice(ArtemisDevice artemisDevice) { artemisDevice.Save(); - _deviceRepository.Save(artemisDevice.DeviceEntity); + _deviceRepository.SaveChanges(); UpdateLeds(); } @@ -236,7 +236,7 @@ public void SaveDevices() { foreach (ArtemisDevice artemisDevice in _devices) artemisDevice.Save(); - _deviceRepository.Save(_devices.Select(d => d.DeviceEntity)); + _deviceRepository.SaveChanges(); UpdateLeds(); } @@ -254,6 +254,8 @@ private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice) { _logger.Information("No device config found for {DeviceInfo}, device hash: {DeviceHashCode}. Adding a new entry", rgbDevice.DeviceInfo, deviceIdentifier); device = new ArtemisDevice(rgbDevice, deviceProvider); + _deviceRepository.Add(device.DeviceEntity); + _deviceRepository.SaveChanges(); } LoadDeviceLayout(device); diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 2f5d69638..033ed81ee 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -807,7 +807,7 @@ private void SavePlugin(Plugin plugin) plugin.Entity.Features.Add(featureInfo.Instance!.Entity); } - _pluginRepository.SavePlugin(plugin.Entity); + _pluginRepository.SaveChanges(); } #endregion diff --git a/src/Artemis.Core/Services/ScriptingService.cs b/src/Artemis.Core/Services/ScriptingService.cs index f768c2028..19011eefa 100644 --- a/src/Artemis.Core/Services/ScriptingService.cs +++ b/src/Artemis.Core/Services/ScriptingService.cs @@ -12,7 +12,7 @@ internal class ScriptingService : IScriptingService private readonly IPluginManagementService _pluginManagementService; private readonly IProfileService _profileService; private readonly List _scriptingProviders; - + public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService) { _pluginManagementService = pluginManagementService; @@ -29,10 +29,13 @@ public ScriptingService(IPluginManagementService pluginManagementService, IProfi // No need to sub to Deactivated, scripts will deactivate themselves profileService.ProfileActivated += ProfileServiceOnProfileActivated; - foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations) + foreach (ProfileCategory profileCategory in _profileService.ProfileCategories) { - if (profileConfiguration.Profile != null) - InitializeProfileScripts(profileConfiguration.Profile); + foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) + { + if (profileConfiguration.Profile != null) + InitializeProfileScripts(profileConfiguration.Profile); + } } } @@ -112,11 +115,14 @@ private void PluginManagementServiceOnPluginFeatureToggled(object? sender, Plugi { _scriptingProviders.Clear(); _scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType()); - - foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations) + + foreach (ProfileCategory profileCategory in _profileService.ProfileCategories) { - if (profileConfiguration.Profile != null) - InitializeProfileScripts(profileConfiguration.Profile); + foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) + { + if (profileConfiguration.Profile != null) + InitializeProfileScripts(profileConfiguration.Profile); + } } } diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index 2d2dfae7c..47766f5bb 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -16,11 +16,6 @@ public interface IProfileService : IArtemisService /// ReadOnlyCollection ProfileCategories { get; } - /// - /// Gets a read only collection containing all the profile configurations. - /// - ReadOnlyCollection ProfileConfigurations { get; } - /// /// Gets or sets the focused profile configuration which is rendered exclusively. /// @@ -101,16 +96,6 @@ public interface IProfileService : IArtemisService /// void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration); - /// - /// Loads the icon of this profile configuration if needed and puts it into ProfileConfiguration.Icon.FileIcon. - /// - void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration); - - /// - /// Saves the current icon of this profile. - /// - void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration); - /// /// Writes the profile to persistent storage. /// diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 5cafbd7db..bf6aa0f4f 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -25,7 +25,6 @@ internal class ProfileService : IProfileService private readonly IDeviceService _deviceService; 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(); @@ -38,17 +37,17 @@ public ProfileService(ILogger logger, IPluginManagementService pluginManagementService, IInputService inputService, IDeviceService deviceService, - 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)); + ProfileCategories = new ReadOnlyCollection(_profileCategories); + _deviceService.LedsChanged += DeviceServiceOnLedsChanged; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled; @@ -166,50 +165,7 @@ public void RenderProfiles(SKCanvas canvas) } /// - public ReadOnlyCollection ProfileCategories - { - get - { - lock (_profileRepository) - { - return _profileCategories.AsReadOnly(); - } - } - } - - /// - public ReadOnlyCollection ProfileConfigurations - { - get - { - lock (_profileRepository) - { - return _profileCategories.SelectMany(c => c.ProfileConfigurations).ToList().AsReadOnly(); - } - } - } - - /// - public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration) - { - if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon) - return; - - using Stream? stream = _profileCategoryRepository.GetProfileIconStream(profileConfiguration.Entity.FileIconId); - if (stream != null) - profileConfiguration.Icon.SetIconByStream(stream); - } - - /// - public void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration) - { - if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon) - return; - - using Stream? stream = profileConfiguration.Icon.GetIconStream(); - if (stream != null) - _profileCategoryRepository.SaveProfileIconStream(profileConfiguration.Entity, stream); - } + public ReadOnlyCollection ProfileCategories { get; } /// public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration) @@ -226,21 +182,7 @@ public Profile ActivateProfile(ProfileConfiguration profileConfiguration) return profileConfiguration.Profile; } - ProfileEntity? profileEntity; - try - { - profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); - } - catch (Exception e) - { - profileConfiguration.SetBrokenState("Failed to activate profile", e); - throw; - } - - if (profileEntity == null) - throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}"); - - Profile profile = new(profileConfiguration, profileEntity); + Profile profile = new(profileConfiguration, profileConfiguration.Entity.Profile); profile.PopulateLeds(_deviceService.EnabledDevices); if (profile.IsFreshImport) @@ -285,39 +227,34 @@ public void DeleteProfile(ProfileConfiguration profileConfiguration) { DeactivateProfile(profileConfiguration); - ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); - if (profileEntity == null) - return; + ProfileCategory category = profileConfiguration.Category; - profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); - _profileRepository.Remove(profileEntity); - SaveProfileCategory(profileConfiguration.Category); + category.RemoveProfileConfiguration(profileConfiguration); + category.Entity.ProfileConfigurations.Remove(profileConfiguration.Entity); + + _profileCategoryRepository.SaveChanges(); } /// public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) { ProfileCategory profileCategory; - lock (_profileRepository) + if (addToTop) { - if (addToTop) + profileCategory = new ProfileCategory(name, 1); + foreach (ProfileCategory category in _profileCategories) { - profileCategory = new ProfileCategory(name, 1); - foreach (ProfileCategory category in _profileCategories) - { - category.Order++; - category.Save(); - _profileCategoryRepository.Save(category.Entity); - } + category.Order++; + category.Save(); } - else - { - profileCategory = new ProfileCategory(name, _profileCategories.Count + 1); - } - - _profileCategories.Add(profileCategory); - SaveProfileCategory(profileCategory); } + else + { + profileCategory = new ProfileCategory(name, _profileCategories.Count + 1); + } + + _profileCategoryRepository.Add(profileCategory.Entity); + _profileCategories.Add(profileCategory); OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory)); return profileCategory; @@ -326,15 +263,11 @@ public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) /// public void DeleteProfileCategory(ProfileCategory profileCategory) { - List profileConfigurations = profileCategory.ProfileConfigurations.ToList(); - foreach (ProfileConfiguration profileConfiguration in profileConfigurations) + foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations.ToList()) RemoveProfileConfiguration(profileConfiguration); - lock (_profileRepository) - { - _profileCategories.Remove(profileCategory); - _profileCategoryRepository.Remove(profileCategory.Entity); - } + _profileCategories.Remove(profileCategory); + _profileCategoryRepository.Remove(profileCategory.Entity); OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory)); } @@ -343,24 +276,20 @@ public void DeleteProfileCategory(ProfileCategory profileCategory) public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon) { ProfileConfiguration configuration = new(category, name, icon); - ProfileEntity entity = new(); - _profileRepository.Add(entity); - configuration.Entity.ProfileId = entity.Id; category.AddProfileConfiguration(configuration, 0); + SaveProfileCategory(category); return configuration; } /// public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration) { - profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); + ProfileCategory category = profileConfiguration.Category; + category.RemoveProfileConfiguration(profileConfiguration); DeactivateProfile(profileConfiguration); SaveProfileCategory(profileConfiguration.Category); - ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); - if (profileEntity != null) - _profileRepository.Remove(profileEntity); profileConfiguration.Dispose(); } @@ -369,7 +298,7 @@ public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration public void SaveProfileCategory(ProfileCategory profileCategory) { profileCategory.Save(); - _profileCategoryRepository.Save(profileCategory.Entity); + _profileCategoryRepository.SaveChanges(); lock (_profileCategories) { @@ -392,11 +321,13 @@ public void SaveProfile(Profile profile, bool includeChildren) profile.IsFreshImport = false; profile.ProfileEntity.IsFreshImport = false; - _profileRepository.Save(profile.ProfileEntity); + SaveProfileCategory(profile.Configuration.Category); // If the provided profile is external (cloned or from the workshop?) but it is loaded locally too, reload the local instance // A bit dodge but it ensures local instances always represent the latest stored version - ProfileConfiguration? localInstance = ProfileConfigurations.FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id); + ProfileConfiguration? localInstance = ProfileCategories + .SelectMany(c => c.ProfileConfigurations) + .FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id); if (localInstance == null) return; DeactivateProfile(localInstance); @@ -406,12 +337,8 @@ public void SaveProfile(Profile profile, bool includeChildren) /// public async Task ExportProfile(ProfileConfiguration profileConfiguration) { - ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); - if (profileEntity == null) - throw new ArtemisCoreException("Could not locate profile entity"); - - string configurationJson = CoreJson.Serialize(profileConfiguration.Entity); - string profileJson = CoreJson.Serialize(profileEntity); + string configurationJson = CoreJson.Serialize(profileConfiguration.Entity.ProfileConfiguration); + string profileJson = CoreJson.Serialize(profileConfiguration.Entity.Profile); MemoryStream archiveStream = new(); @@ -430,12 +357,11 @@ public async Task ExportProfile(ProfileConfiguration profileConfiguratio await entryStream.WriteAsync(Encoding.Default.GetBytes(profileJson)); } - await using Stream? iconStream = profileConfiguration.Icon.GetIconStream(); - if (iconStream != null) + if (profileConfiguration.Icon.IconBytes != null) { ZipArchiveEntry iconEntry = archive.CreateEntry("icon.png"); await using Stream entryStream = iconEntry.Open(); - await iconStream.CopyToAsync(entryStream); + await entryStream.WriteAsync(profileConfiguration.Icon.IconBytes); } } @@ -495,26 +421,26 @@ public async Task ImportProfile(Stream archiveStream, Prof if (markAsFreshImport) profileEntity.IsFreshImport = true; - if (_profileRepository.Get(profileEntity.Id) == null) - _profileRepository.Add(profileEntity); - else + if (makeUnique && ProfileCategories.SelectMany(c => c.ProfileConfigurations).Any(c => c.ProfileId == profileEntity.Id)) throw new ArtemisCoreException($"Cannot import this profile without {nameof(makeUnique)} being true"); - // A new GUID will be given on save - configurationEntity.FileIconId = Guid.Empty; - ProfileConfiguration profileConfiguration = new(category, configurationEntity); - if (nameAffix != null) - profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}"; - + ProfileContainerEntity containerEntity = new() {ProfileConfiguration = configurationEntity, Profile = profileEntity}; // If an icon was provided, import that as well if (iconEntry != null) { await using Stream iconStream = iconEntry.Open(); - profileConfiguration.Icon.SetIconByStream(iconStream); - SaveProfileConfigurationIcon(profileConfiguration); + using MemoryStream ms = new(); + await iconStream.CopyToAsync(ms); + containerEntity.Icon = ms.ToArray(); } - profileConfiguration.Entity.ProfileId = profileEntity.Id; + // A new GUID will be given on save + configurationEntity.FileIconId = Guid.Empty; + ProfileConfiguration profileConfiguration = new(category, containerEntity); + if (nameAffix != null) + profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}"; + + profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id; category.AddProfileConfiguration(profileConfiguration, targetIndex); List modules = _pluginManagementService.GetFeaturesOfType(); @@ -548,7 +474,7 @@ public void AdaptProfile(Profile profile) renderProfileElement.Save(); _logger.Debug("Adapt profile - Saving " + profile); - _profileRepository.Save(profile.ProfileEntity); + SaveProfileCategory(profile.Configuration.Category); } private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) @@ -565,7 +491,7 @@ private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJs return; configurationJson["Version"] ??= 0; - + foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version)) { if (profileMigrator.Version <= configurationJson["Version"]!.GetValue()) @@ -581,27 +507,27 @@ private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJs /// private void ActiveProfilesPopulateLeds() { - foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) + foreach (ProfileCategory profileCategory in ProfileCategories) { - if (profileConfiguration.Profile == null) continue; - profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices); + foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) + { + if (profileConfiguration.Profile == null) continue; + profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices); - if (!profileConfiguration.Profile.IsFreshImport) continue; - _logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile); - AdaptProfile(profileConfiguration.Profile); + if (!profileConfiguration.Profile.IsFreshImport) continue; + _logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile); + AdaptProfile(profileConfiguration.Profile); + } } } private void UpdateModules() { - lock (_profileRepository) + List modules = _pluginManagementService.GetFeaturesOfType(); + foreach (ProfileCategory profileCategory in ProfileCategories) { - List modules = _pluginManagementService.GetFeaturesOfType(); - foreach (ProfileCategory profileCategory in _profileCategories) - { - foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) - profileConfiguration.LoadModules(modules); - } + foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) + profileConfiguration.LoadModules(modules); } } diff --git a/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj b/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj new file mode 100644 index 000000000..f35f98a79 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Artemis.Storage.Migrator/Program.cs b/src/Artemis.Storage.Migrator/Program.cs new file mode 100644 index 000000000..6ed140d3d --- /dev/null +++ b/src/Artemis.Storage.Migrator/Program.cs @@ -0,0 +1,21 @@ +using Artemis.Core.DryIoc; +using DryIoc; +using Microsoft.EntityFrameworkCore; + +namespace Artemis.Storage.Migrator; + +class Program +{ + static void Main(string[] args) + { + Container container = new Container(rules => rules + .WithMicrosoftDependencyInjectionRules() + .WithConcreteTypeDynamicRegistrations() + .WithoutThrowOnRegisteringDisposableTransient()); + + container.RegisterCore(); + + container.Resolve().Database.EnsureCreated(); + + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/artemis.db b/src/Artemis.Storage.Migrator/artemis.db new file mode 100644 index 0000000000000000000000000000000000000000..286e00251800998db3fc3ad17b0ae79573adf4ff GIT binary patch literal 86016 zcmeI&&u`mg7{Kwk?UMdj+x_Td(6q@!LRzG;{zB>s7t+)%*3zUT-Dp85GL73=WJ!sg zK@S|#F%J9%+&S}iaOcJW2`(J?3pnyRvE4d(W0d2pZ`3G>{l3ooe4fXz-x{;cgNPVPfsZ?r0Jnx8S@R$>Crh*URRk`wh)Z2;FqmzH;7ynHyrG8B< z{I>YZ_34G5XKQo6&-^sIGTol}JhL#poPLo0W6Het*TloACFOh3aQsL2gQbjmys308 zyIHrsH4bb0?beCiunMiFYuZh#tv6lUJr6gVu65dKpBr^~yE}!7oF?6Q9oT*x_jZ^8=X_Txnr4br)>oz?``XU5W^mBu{1p9 z;YPK7$KWvjXouVNh8p?CZ0?nHGox;9DnD4R`DMeB$0*-=%OBf8eyga*@XXI@;*mA# zS?xeSJkUyI@qAh=YK3yCdQiz5r32CXNtniHcC*@k#n{VNjqT9ZdLdse~322j^#&Ja1XU(VJe$i?0$DsPF2P;aZBGEPi!}()BB2F!#fQ@=r~X?tw!Mb-l)#YQiU6{}&Hj^zZe3(^^Y^f_lV9oKA#?Bv}bf5KhFzPylD3-?CF9(-eMHF&ppaepqOE-x#uzKYCn z;b{}pc(~zuH147OV*p6{N+e3=i_t||Xf+z}dBl?kD0xvt$jb^4_je3~85?GlL!Pz93*kjAuel?+zj^bM?6HyKT#A#x>XylSSPA znDZ~l`u_8C#}*+J&GS}AT=Pa1S+R9`wOL>G@+V2Q-~a0Go}r!lW?NiwT<>x))V&Z+ z>cP%|>NJ{X9oMK^;@0cL7MJk2W_#w@nRf*kXyaA#R9q$drBz$?-(2e+Dj9WSL%AsT zrkq+$-`TTI`#;*9k8Q^lw|&x$6VWP{wz;hN@R;6N9;w;EdzBFflAH?SB&c_eUHPOS zB#eo)`o+em$yfS4wN>fnNpXuk?cHY2i>I>qXG;9>KmY**5I_I{1Q0*~0R#|00D(7D z;Jz}ssjjW8y}z=)@_21!eRFNKW;AWrHXDy0tvp(r6>%wxf2YJB4+IcE009ILKmY** z5I_I{1Q2+m1>TMaCAbPO?91|e|2q1s}0tg_000IagfB*ul z|5F19Ab Plugins => Set(); public DbSet PluginSettings => Set(); public DbSet ProfileCategories => Set(); - public DbSet Profiles => Set(); public DbSet Releases => Set(); + public string DataFolder { get; set; } = string.Empty; + public JsonSerializerOptions JsonSerializerOptions { get; set; } = JsonSerializerOptions.Default; + + /// + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite($"Data Source={Path.Combine(DataFolder, "artemis.db")}"); + } + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .OwnsOne(d => d.InputIdentifiers, builder => builder.ToJson()) .OwnsOne(d => d.InputMappings, builder => builder.ToJson()); - - modelBuilder.Entity() - .OwnsOne(e => e.Metadata, builder => builder.ToJson()); - modelBuilder.Entity() - .OwnsOne(s => s.Value, builder => builder.ToJson()); + modelBuilder.Entity() + .Property(e => e.Metadata) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions) ?? new Dictionary()); modelBuilder.Entity() - .OwnsOne(c => c.ProfileConfiguration, builder => builder.ToJson()) - .OwnsOne(c => c.Profile, builder => builder.ToJson()); + .Property(e => e.ProfileConfiguration) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize(v, JsonSerializerOptions) ?? new ProfileConfigurationEntity()); + + modelBuilder.Entity() + .Property(e => e.Profile) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize(v, JsonSerializerOptions) ?? new ProfileEntity()); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs index 611200781..0a0ca3b16 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs @@ -24,6 +24,8 @@ public PluginEntity() /// public class PluginFeatureEntity { + public Guid Id { get; set; } + public string Type { get; set; } = string.Empty; public bool IsEnabled { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs index 8be8cefb5..1d604c28a 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs @@ -1,5 +1,4 @@ using System; -using System.Text.Json.Nodes; namespace Artemis.Storage.Entities.Plugins; @@ -12,5 +11,5 @@ public class PluginSettingEntity public Guid PluginGuid { get; set; } public string Name { get; set; } = string.Empty; - public JsonNode Value { get; set; } = new JsonObject(); + public string Value { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs index 7bdee9ecf..02a797011 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs @@ -13,11 +13,4 @@ public class ProfileCategoryEntity public int Order { get; set; } public List ProfileConfigurations { get; set; } = new(); -} - -public class ProfileContainerEntity -{ - public byte[] Icon { get; set; } = Array.Empty(); - public ProfileConfigurationEntity ProfileConfiguration { get; set; } = new(); - public ProfileEntity Profile { get; set; } = new(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileContainerEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileContainerEntity.cs new file mode 100644 index 000000000..b6360ab76 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/ProfileContainerEntity.cs @@ -0,0 +1,14 @@ +using System; + +namespace Artemis.Storage.Entities.Profile; + +public class ProfileContainerEntity +{ + public Guid Id { get; set; } + public byte[] Icon { get; set; } = Array.Empty(); + + public ProfileCategoryEntity ProfileCategory { get; set; } = null!; + + public ProfileConfigurationEntity ProfileConfiguration { get; set; } = new(); + public ProfileEntity Profile { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs index 73a063003..cd828721c 100644 --- a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs @@ -18,5 +18,5 @@ public class EntryEntity public string ReleaseVersion { get; set; } = string.Empty; public DateTimeOffset InstalledAt { get; set; } - public Dictionary? Metadata { get; set; } + public Dictionary Metadata { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs b/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs new file mode 100644 index 000000000..14b41beeb --- /dev/null +++ b/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs @@ -0,0 +1,323 @@ +// +using System; +using Artemis.Storage; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + [DbContext(typeof(ArtemisDbContext))] + [Migration("20240308203921_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Releases"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Plugins"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PluginEntityId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PluginEntityId"); + + b.ToTable("PluginFeatureEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PluginGuid") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PluginSettings"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCollapsed") + .HasColumnType("INTEGER"); + + b.Property("IsSuspended") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ProfileCategories"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Profile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileCategoryId") + .HasColumnType("TEXT"); + + b.Property("ProfileConfiguration") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProfileCategoryId"); + + b.ToTable("ProfileContainerEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BlueScale") + .HasColumnType("REAL"); + + b.Property("Categories") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceProvider") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GreenScale") + .HasColumnType("REAL"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LayoutParameter") + .HasColumnType("TEXT"); + + b.Property("LayoutType") + .HasColumnType("TEXT"); + + b.Property("LogicalLayout") + .HasColumnType("TEXT"); + + b.Property("PhysicalLayout") + .HasColumnType("INTEGER"); + + b.Property("RedScale") + .HasColumnType("REAL"); + + b.Property("Rotation") + .HasColumnType("REAL"); + + b.Property("Scale") + .HasColumnType("REAL"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("ZIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntryId") + .HasColumnType("INTEGER"); + + b.Property("EntryType") + .HasColumnType("INTEGER"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ReleaseId") + .HasColumnType("INTEGER"); + + b.Property("ReleaseVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null) + .WithMany("Features") + .HasForeignKey("PluginEntityId"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory") + .WithMany("ProfileConfigurations") + .HasForeignKey("ProfileCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProfileCategory"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.OwnsOne("System.Collections.Generic.List", "InputIdentifiers", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputIdentifiers"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.OwnsOne("System.Collections.Generic.List", "InputMappings", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputMappings"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.Navigation("InputIdentifiers") + .IsRequired(); + + b.Navigation("InputMappings") + .IsRequired(); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Navigation("ProfileConfigurations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Artemis.Storage/Migrations/20240308203921_Initial.cs b/src/Artemis.Storage/Migrations/20240308203921_Initial.cs new file mode 100644 index 000000000..607c92cc6 --- /dev/null +++ b/src/Artemis.Storage/Migrations/20240308203921_Initial.cs @@ -0,0 +1,194 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Devices", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + DeviceProvider = table.Column(type: "TEXT", nullable: false), + X = table.Column(type: "REAL", nullable: false), + Y = table.Column(type: "REAL", nullable: false), + Rotation = table.Column(type: "REAL", nullable: false), + Scale = table.Column(type: "REAL", nullable: false), + ZIndex = table.Column(type: "INTEGER", nullable: false), + RedScale = table.Column(type: "REAL", nullable: false), + GreenScale = table.Column(type: "REAL", nullable: false), + BlueScale = table.Column(type: "REAL", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false), + PhysicalLayout = table.Column(type: "INTEGER", nullable: false), + LogicalLayout = table.Column(type: "TEXT", nullable: true), + LayoutType = table.Column(type: "TEXT", nullable: true), + LayoutParameter = table.Column(type: "TEXT", nullable: true), + Categories = table.Column(type: "TEXT", nullable: false), + InputIdentifiers = table.Column(type: "TEXT", nullable: false), + InputMappings = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Devices", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Entries", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + EntryId = table.Column(type: "INTEGER", nullable: false), + EntryType = table.Column(type: "INTEGER", nullable: false), + Author = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + ReleaseId = table.Column(type: "INTEGER", nullable: false), + ReleaseVersion = table.Column(type: "TEXT", nullable: false), + InstalledAt = table.Column(type: "TEXT", nullable: false), + Metadata = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Entries", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Plugins", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Plugins", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PluginSettings", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + PluginGuid = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PluginSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ProfileCategories", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + IsCollapsed = table.Column(type: "INTEGER", nullable: false), + IsSuspended = table.Column(type: "INTEGER", nullable: false), + Order = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProfileCategories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Releases", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Version = table.Column(type: "TEXT", nullable: false), + InstalledAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Releases", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PluginFeatureEntity", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "TEXT", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false), + PluginEntityId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PluginFeatureEntity", x => x.Id); + table.ForeignKey( + name: "FK_PluginFeatureEntity_Plugins_PluginEntityId", + column: x => x.PluginEntityId, + principalTable: "Plugins", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "ProfileContainerEntity", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Icon = table.Column(type: "BLOB", nullable: false), + ProfileCategoryId = table.Column(type: "TEXT", nullable: false), + ProfileConfiguration = table.Column(type: "TEXT", nullable: false), + Profile = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProfileContainerEntity", x => x.Id); + table.ForeignKey( + name: "FK_ProfileContainerEntity_ProfileCategories_ProfileCategoryId", + column: x => x.ProfileCategoryId, + principalTable: "ProfileCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PluginFeatureEntity_PluginEntityId", + table: "PluginFeatureEntity", + column: "PluginEntityId"); + + migrationBuilder.CreateIndex( + name: "IX_ProfileContainerEntity_ProfileCategoryId", + table: "ProfileContainerEntity", + column: "ProfileCategoryId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Devices"); + + migrationBuilder.DropTable( + name: "Entries"); + + migrationBuilder.DropTable( + name: "PluginFeatureEntity"); + + migrationBuilder.DropTable( + name: "PluginSettings"); + + migrationBuilder.DropTable( + name: "ProfileContainerEntity"); + + migrationBuilder.DropTable( + name: "Releases"); + + migrationBuilder.DropTable( + name: "Plugins"); + + migrationBuilder.DropTable( + name: "ProfileCategories"); + } + } +} diff --git a/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs new file mode 100644 index 000000000..bd8df8c96 --- /dev/null +++ b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs @@ -0,0 +1,320 @@ +// +using System; +using Artemis.Storage; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + [DbContext(typeof(ArtemisDbContext))] + partial class ArtemisDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Releases"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Plugins"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PluginEntityId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PluginEntityId"); + + b.ToTable("PluginFeatureEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PluginGuid") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PluginSettings"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCollapsed") + .HasColumnType("INTEGER"); + + b.Property("IsSuspended") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ProfileCategories"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Profile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileCategoryId") + .HasColumnType("TEXT"); + + b.Property("ProfileConfiguration") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProfileCategoryId"); + + b.ToTable("ProfileContainerEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BlueScale") + .HasColumnType("REAL"); + + b.Property("Categories") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceProvider") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GreenScale") + .HasColumnType("REAL"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LayoutParameter") + .HasColumnType("TEXT"); + + b.Property("LayoutType") + .HasColumnType("TEXT"); + + b.Property("LogicalLayout") + .HasColumnType("TEXT"); + + b.Property("PhysicalLayout") + .HasColumnType("INTEGER"); + + b.Property("RedScale") + .HasColumnType("REAL"); + + b.Property("Rotation") + .HasColumnType("REAL"); + + b.Property("Scale") + .HasColumnType("REAL"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("ZIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntryId") + .HasColumnType("INTEGER"); + + b.Property("EntryType") + .HasColumnType("INTEGER"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ReleaseId") + .HasColumnType("INTEGER"); + + b.Property("ReleaseVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null) + .WithMany("Features") + .HasForeignKey("PluginEntityId"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory") + .WithMany("ProfileConfigurations") + .HasForeignKey("ProfileCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProfileCategory"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.OwnsOne("System.Collections.Generic.List", "InputIdentifiers", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputIdentifiers"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.OwnsOne("System.Collections.Generic.List", "InputMappings", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputMappings"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.Navigation("InputIdentifiers") + .IsRequired(); + + b.Navigation("InputMappings") + .IsRequired(); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Navigation("ProfileConfigurations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs deleted file mode 100644 index 5133c0328..000000000 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Storage.Entities.Profile; - -namespace Artemis.Storage.Repositories.Interfaces; - -public interface IProfileRepository : IRepository -{ - void Add(ProfileContainerEntity profileContainerEntity); - void Remove(ProfileContainerEntity profileContainerEntity); - List GetAll(); - ProfileContainerEntity? Get(Guid id); - void SaveChanges(); -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs deleted file mode 100644 index 0a2c1b88b..000000000 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Storage.Entities.Profile; -using Artemis.Storage.Repositories.Interfaces; -using LiteDB; - -namespace Artemis.Storage.Repositories; - -internal class ProfileRepository : IProfileRepository -{ - private readonly ArtemisDbContext _dbContext; - - public ProfileRepository(ArtemisDbContext dbContext) - { - _dbContext = dbContext; - } - - public void Add(ProfileContainerEntity profileContainerEntity) - { - _dbContext.Profiles.Add(profileContainerEntity); - SaveChanges(); - } - - public void Remove(ProfileContainerEntity profileContainerEntity) - { - _dbContext.Profiles.Remove(profileContainerEntity); - SaveChanges(); - } - - public List GetAll() - { - return _dbContext.Profiles.ToList(); - } - - public ProfileContainerEntity? Get(Guid id) - { - return _dbContext.Profiles.FirstOrDefault(c => c.Profile.Id == id); - } - - public void SaveChanges() - { - _dbContext.SaveChanges(); - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs index 78fcc3643..7b612a927 100644 --- a/src/Artemis.Storage/StorageManager.cs +++ b/src/Artemis.Storage/StorageManager.cs @@ -59,4 +59,12 @@ public static LiteRepository CreateRepository(string dataFolder) throw new Exception($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e); } } + + public static ArtemisDbContext CreateDbContext(string dataFolder) + { + return new ArtemisDbContext() + { + DataFolder = dataFolder + }; + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs index dd652f0d8..7f93ea093 100644 --- a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs @@ -46,17 +46,10 @@ private void Update() ? new MaterialIcon {Kind = parsedIcon!} : new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; } + else if (ConfigurationIcon.IconBytes != null) + Dispatcher.UIThread.Post(() => LoadFromBitmap(ConfigurationIcon, new MemoryStream(ConfigurationIcon.IconBytes)), DispatcherPriority.ApplicationIdle); else - { - Dispatcher.UIThread.Post(() => - { - Stream? stream = ConfigurationIcon?.GetIconStream(); - if (stream == null || ConfigurationIcon == null) - Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; - else - LoadFromBitmap(ConfigurationIcon, stream); - }, DispatcherPriority.ApplicationIdle); - } + Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; } catch (Exception) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 1b6af5381..eca2ee350 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -186,7 +186,7 @@ private void MainWindowServiceOnMainWindowUnfocused(object? sender, EventArgs e) /// public override async Task OnNavigating(ProfileEditorViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { - ProfileConfiguration? profileConfiguration = _profileService.ProfileConfigurations.FirstOrDefault(c => c.ProfileId == parameters.ProfileId); + ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId); // If the profile doesn't exist, cancel navigation if (profileConfiguration == null) diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs index 1f1c9c500..aa79c5e75 100644 --- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs @@ -123,7 +123,6 @@ private async Task ExecuteConfirm() await SaveIcon(); - _profileService.SaveProfileConfigurationIcon(ProfileConfiguration); _profileService.SaveProfileCategory(_profileCategory); Close(ProfileConfiguration); } @@ -144,7 +143,7 @@ private void LoadIcon() { try { - Stream? iconStream = _profileConfiguration.Icon.GetIconStream(); + Stream? iconStream = _profileConfiguration.Icon.IconBytes != null ? new MemoryStream(_profileConfiguration.Icon.IconBytes) : null; SelectedBitmapSource = iconStream != null ? new Bitmap(iconStream) : null; } catch (Exception e) diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationViewModel.cs index bfc179095..99f9330d7 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarProfileConfigurationViewModel.cs @@ -40,15 +40,6 @@ public SidebarProfileConfigurationViewModel(IRouter router, ProfileConfiguration .Select(p => p == null) .ToProperty(this, vm => vm.IsDisabled) .DisposeWith(d)); - - try - { - _profileService.LoadProfileConfigurationIcon(ProfileConfiguration); - } - catch (Exception) - { - // ignored, too bad but don't crash over corrupt icons - } } public ProfileConfiguration ProfileConfiguration { get; } diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs index 4a5b9cae9..5afebcf4b 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -27,10 +28,7 @@ public ProfileSelectionStepViewModel(IProfileService profileService, ProfilePrev _profileService = profileService; // Use copies of the profiles, the originals are used by the core and could be disposed - Profiles = new ObservableCollection(_profileService.ProfileConfigurations.Select(_profileService.CloneProfileConfiguration)); - foreach (ProfileConfiguration profileConfiguration in Profiles) - _profileService.LoadProfileConfigurationIcon(profileConfiguration); - + Profiles = new ObservableCollection(_profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).Select(_profileService.CloneProfileConfiguration)); ProfilePreview = profilePreviewViewModel; GoBack = ReactiveCommand.Create(() => State.ChangeScreen()); @@ -70,7 +68,7 @@ private void ExecuteContinue() State.EntrySource = new ProfileEntrySource(SelectedProfile, SelectedProfile.GetFeatureDependencies().Distinct().ToList()); State.Name = SelectedProfile.Name; - State.Icon = SelectedProfile.Icon.GetIconStream(); + State.Icon = SelectedProfile.Icon.IconBytes != null ? new MemoryStream(SelectedProfile.Icon.IconBytes) : null; // Render the material icon of the profile if (State.Icon == null && SelectedProfile.Icon.IconName != null) diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs index 218665d05..4ee9e0cb5 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs @@ -74,7 +74,7 @@ public async Task UninstallAsync(InstalledEntry installedE try { // Find the profile if still there - ProfileConfiguration? profile = _profileService.ProfileConfigurations.FirstOrDefault(c => c.ProfileId == profileId); + ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId); if (profile != null) _profileService.DeleteProfile(profile); diff --git a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs index 1f722cc5d..90aa4108e 100644 --- a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs +++ b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs @@ -1,16 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using System.Text.Json.Nodes; using Artemis.Core; using Artemis.Storage.Entities.Workshop; -using Artemis.WebClient.Workshop.Exceptions; namespace Artemis.WebClient.Workshop.Models; public class InstalledEntry { - private static readonly JsonSerializerOptions JsonSerializerOptions = CoreJson.GetJsonSerializerOptions(); - private Dictionary _metadata = new(); + private Dictionary _metadata = new(); internal InstalledEntry(EntryEntity entity) { @@ -56,7 +52,7 @@ internal void Load() ReleaseVersion = Entity.ReleaseVersion; InstalledAt = Entity.InstalledAt; - _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); + _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); } internal void Save() @@ -71,7 +67,7 @@ internal void Save() Entity.ReleaseVersion = ReleaseVersion; Entity.InstalledAt = InstalledAt; - Entity.Metadata = new Dictionary(_metadata); + Entity.Metadata = new Dictionary(_metadata); } /// @@ -84,29 +80,14 @@ internal void Save() /// if the metadata contains an element with the specified key; otherwise, . public bool TryGetMetadata(string key, [NotNullWhen(true)] out T? value) { - if (!_metadata.TryGetValue(key, out JsonNode? jsonNode)) + if (!_metadata.TryGetValue(key, out object? objectValue) || objectValue is not T result) { value = default; return false; } - try - { - T? deserialized = jsonNode.Deserialize(JsonSerializerOptions); - if (deserialized != null) - { - value = deserialized; - return true; - } - - value = default; - return false; - } - catch (Exception) - { - value = default; - return false; - } + value = result; + return true; } /// @@ -116,8 +97,7 @@ public bool TryGetMetadata(string key, [NotNullWhen(true)] out T? value) /// The value to set. public void SetMetadata(string key, object value) { - JsonNode? jsonNode = JsonSerializer.SerializeToNode(value, JsonSerializerOptions); - _metadata[key] = jsonNode ?? throw new ArtemisWorkshopException("Failed to serialize metadata value"); + _metadata[key] = value; } /// diff --git a/src/Artemis.sln b/src/Artemis.sln index e7643caba..984eb7997 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Storage.Migrator", "Artemis.Storage.Migrator\Artemis.Storage.Migrator.csproj", "{D7B0966D-774A-40E4-9455-00C1261ACB6A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -71,6 +73,10 @@ Global {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Debug|x64.Build.0 = Debug|x64 {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.ActiveCfg = Release|x64 {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.Build.0 = Release|x64 + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Debug|x64.Build.0 = Debug|Any CPU + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Release|x64.ActiveCfg = Release|Any CPU + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index f1830bf8a..b88396b71 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -30,6 +30,7 @@ + From 70994feb32abc6bcfaaa8bcc78d3f8723a135e60 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Sat, 9 Mar 2024 10:54:12 +0100 Subject: [PATCH 03/14] Storage - Fixed remaining issues so far --- src/Artemis.Core/Constants.cs | 3 +- .../DryIoc/ContainerExtensions.cs | 1 - .../Models/Profile/ProfileCategory.cs | 42 ++--- .../ProfileConfigurationIcon.cs | 10 +- src/Artemis.Core/Plugins/Plugin.cs | 6 +- src/Artemis.Core/Services/DeviceService.cs | 1 - .../Services/PluginManagementService.cs | 14 +- src/Artemis.Core/Services/RenderService.cs | 2 +- .../Services/Storage/ProfileService.cs | 150 +++++++++--------- src/Artemis.Storage.Migrator/Program.cs | 2 - src/Artemis.Storage/ArtemisDbContext.cs | 3 +- .../Entities/Profile/FolderEntity.cs | 4 - .../Entities/Profile/LayerEntity.cs | 4 - .../Repositories/DeviceRepository.cs | 4 +- .../Repositories/EntryRepository.cs | 4 +- .../Interfaces/IDeviceRepository.cs | 2 +- .../Interfaces/IEntryRepository.cs | 2 +- .../Interfaces/IPluginRepository.cs | 1 - .../Repositories/PluginRepository.cs | 8 +- src/Artemis.Storage/StorageManager.cs | 42 ++--- .../Screens/Settings/Tabs/AboutTabView.axaml | 20 +-- .../Settings/Tabs/GeneralTabViewModel.cs | 2 +- .../ProfileConfigurationEditViewModel.cs | 1 + .../SurfaceEditor/SurfaceDeviceViewModel.cs | 2 +- .../SurfaceEditor/SurfaceEditorViewModel.cs | 2 +- .../Repositories/AuthenticationRepository.cs | 21 ++- .../Services/WorkshopService.cs | 7 +- 27 files changed, 168 insertions(+), 192 deletions(-) diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index c61aa021c..79d19cc48 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Text.Json; using Artemis.Core.JsonConverters; +using Artemis.Storage.Entities.Plugins; namespace Artemis.Core; @@ -90,7 +91,7 @@ public static class Constants /// /// The plugin used by core components of Artemis /// - public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null); + public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), new PluginEntity(){Id = CorePluginInfo.Guid}, false); /// /// A read-only collection containing all primitive numeric types diff --git a/src/Artemis.Core/DryIoc/ContainerExtensions.cs b/src/Artemis.Core/DryIoc/ContainerExtensions.cs index ba0ede352..62712427f 100644 --- a/src/Artemis.Core/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.Core/DryIoc/ContainerExtensions.cs @@ -30,7 +30,6 @@ public static void RegisterCore(this IContainer container) container.RegisterMany(coreAssembly, type => type.IsAssignableTo(), Reuse.Singleton, setup: Setup.With(condition: HasAccessToProtectedService)); // Bind storage - container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton); container.RegisterDelegate(() => StorageManager.CreateDbContext(Constants.DataFolder), Reuse.Transient); container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton); diff --git a/src/Artemis.Core/Models/Profile/ProfileCategory.cs b/src/Artemis.Core/Models/Profile/ProfileCategory.cs index 63dbafffa..cdab02e8a 100644 --- a/src/Artemis.Core/Models/Profile/ProfileCategory.cs +++ b/src/Artemis.Core/Models/Profile/ProfileCategory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using Artemis.Storage.Entities.Profile; namespace Artemis.Core; @@ -15,7 +16,6 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel /// public static readonly ProfileCategory Empty = new("Empty", -1); - private readonly List _profileConfigurations = new(); private bool _isCollapsed; private bool _isSuspended; private string _name; @@ -31,14 +31,16 @@ internal ProfileCategory(string name, int order) _name = name; _order = order; Entity = new ProfileCategoryEntity(); - ProfileConfigurations = new ReadOnlyCollection(_profileConfigurations); + ProfileConfigurations = new ReadOnlyCollection([]); + + Save(); } internal ProfileCategory(ProfileCategoryEntity entity) { _name = null!; Entity = entity; - ProfileConfigurations = new ReadOnlyCollection(_profileConfigurations); + ProfileConfigurations = new ReadOnlyCollection([]); Load(); } @@ -83,7 +85,7 @@ public bool IsSuspended /// /// Gets a read only collection of the profiles inside this category /// - public ReadOnlyCollection ProfileConfigurations { get; } + public ReadOnlyCollection ProfileConfigurations { get; private set; } /// /// Gets the unique ID of this category @@ -98,27 +100,30 @@ public bool IsSuspended /// public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex) { + List targetList = ProfileConfigurations.ToList(); + // TODO: Look into this, it doesn't seem to make sense // Removing the original will shift every item in the list forwards, keep that in mind with the target index - if (configuration.Category == this && targetIndex != null && targetIndex.Value > _profileConfigurations.IndexOf(configuration)) + if (configuration.Category == this && targetIndex != null && targetIndex.Value > targetList.IndexOf(configuration)) targetIndex -= 1; configuration.Category.RemoveProfileConfiguration(configuration); - + if (targetIndex != null) { - targetIndex = Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count); - _profileConfigurations.Insert(targetIndex.Value, configuration); + targetIndex = Math.Clamp(targetIndex.Value, 0, targetList.Count); + targetList.Insert(targetIndex.Value, configuration); } else { - _profileConfigurations.Add(configuration); + targetList.Add(configuration); } configuration.Category = this; + ProfileConfigurations = new ReadOnlyCollection(targetList); - for (int index = 0; index < _profileConfigurations.Count; index++) - _profileConfigurations[index].Order = index; + for (int index = 0; index < ProfileConfigurations.Count; index++) + ProfileConfigurations[index].Order = index; OnProfileConfigurationAdded(new ProfileConfigurationEventArgs(configuration)); } @@ -156,11 +161,10 @@ protected virtual void OnProfileConfigurationRemoved(ProfileConfigurationEventAr internal void RemoveProfileConfiguration(ProfileConfiguration configuration) { - if (!_profileConfigurations.Remove(configuration)) - return; - - for (int index = 0; index < _profileConfigurations.Count; index++) - _profileConfigurations[index].Order = index; + ProfileConfigurations = new ReadOnlyCollection(ProfileConfigurations.Where(pc => pc != configuration).ToList()); + for (int index = 0; index < ProfileConfigurations.Count; index++) + ProfileConfigurations[index].Order = index; + OnProfileConfigurationRemoved(new ProfileConfigurationEventArgs(configuration)); } @@ -174,9 +178,7 @@ public void Load() IsSuspended = Entity.IsSuspended; Order = Entity.Order; - _profileConfigurations.Clear(); - foreach (ProfileContainerEntity entityProfileConfiguration in Entity.ProfileConfigurations) - _profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration)); + ProfileConfigurations = new ReadOnlyCollection(Entity.ProfileConfigurations.Select(pc => new ProfileConfiguration(this, pc)).ToList()); } /// @@ -188,7 +190,7 @@ public void Save() Entity.Order = Order; Entity.ProfileConfigurations.Clear(); - foreach (ProfileConfiguration profileConfiguration in _profileConfigurations) + foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) Entity.ProfileConfigurations.Add(profileConfiguration.Entity); } diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs index f2916190e..c935836db 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs @@ -115,12 +115,12 @@ public void Load() { IconType = (ProfileConfigurationIconType) _entity.ProfileConfiguration.IconType; Fill = _entity.ProfileConfiguration.IconFill; - if (IconType != ProfileConfigurationIconType.MaterialIcon) - return; - - IconName = _entity.ProfileConfiguration.MaterialIcon; - IconBytes = IconType == ProfileConfigurationIconType.BitmapImage ? _entity.Icon : null; + if (IconType == ProfileConfigurationIconType.MaterialIcon) + IconName = _entity.ProfileConfiguration.MaterialIcon; + else + IconBytes = _entity.Icon; + OnIconUpdated(); } diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index ff90181bc..2b5ce51d1 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -23,14 +23,14 @@ public class Plugin : CorePropertyChanged, IDisposable private bool _isEnabled; - internal Plugin(PluginInfo info, DirectoryInfo directory, PluginEntity? pluginEntity) + internal Plugin(PluginInfo info, DirectoryInfo directory, PluginEntity pluginEntity, bool loadedFromStorage) { Info = info; Directory = directory; - Entity = pluginEntity ?? new PluginEntity {Id = Guid}; + Entity = pluginEntity; Info.Plugin = this; - _loadedFromStorage = pluginEntity != null; + _loadedFromStorage = loadedFromStorage; _features = new List(); _profilers = new List(); diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index d110c90a4..46399722e 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -255,7 +255,6 @@ private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice) _logger.Information("No device config found for {DeviceInfo}, device hash: {DeviceHashCode}. Adding a new entry", rgbDevice.DeviceInfo, deviceIdentifier); device = new ArtemisDevice(rgbDevice, deviceProvider); _deviceRepository.Add(device.DeviceEntity); - _deviceRepository.SaveChanges(); } LoadDeviceLayout(device); diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 033ed81ee..5b31b31d9 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -41,7 +41,7 @@ public PluginManagementService(IContainer container, ILogger logger, IPluginRepo _pluginRepository = pluginRepository; _deviceRepository = deviceRepository; _plugins = new List(); - + StartHotReload(); } @@ -372,7 +372,15 @@ public void UnloadPlugins() } // Load the entity and fall back on creating a new one - Plugin plugin = new(pluginInfo, directory, _pluginRepository.GetPluginByGuid(pluginInfo.Guid)); + PluginEntity? entity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid); + bool loadedFromStorage = entity != null; + if (entity == null) + { + entity = new PluginEntity {Id = pluginInfo.Guid}; + _pluginRepository.AddPlugin(entity); + } + + Plugin plugin = new(pluginInfo, directory, entity, loadedFromStorage); OnPluginLoading(new PluginEventArgs(plugin)); // Locate the main assembly entry @@ -796,7 +804,7 @@ public void DisablePluginFeature(PluginFeature pluginFeature, bool saveState) } #endregion - + #region Storage private void SavePlugin(Plugin plugin) diff --git a/src/Artemis.Core/Services/RenderService.cs b/src/Artemis.Core/Services/RenderService.cs index bd73a0f0d..25c6e6185 100644 --- a/src/Artemis.Core/Services/RenderService.cs +++ b/src/Artemis.Core/Services/RenderService.cs @@ -40,7 +40,7 @@ public RenderService(ILogger logger, ISettingsService settingsService, IDeviceSe _graphicsContextProviders = graphicsContextProviders; _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30); - _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25); + _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5); _preferredGraphicsContext = settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); _targetFrameRateSetting.SettingChanged += OnRenderSettingsChanged; _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged; diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index bf6aa0f4f..7e0112af7 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; @@ -24,7 +25,6 @@ internal class ProfileService : IProfileService private readonly IPluginManagementService _pluginManagementService; private readonly IDeviceService _deviceService; private readonly List _pendingKeyboardEvents = new(); - private readonly List _profileCategories; private readonly List _profileMigrators; private readonly List _renderExceptions = new(); private readonly List _updateExceptions = new(); @@ -44,17 +44,16 @@ public ProfileService(ILogger logger, _pluginManagementService = pluginManagementService; _deviceService = deviceService; _profileMigrators = profileMigrators; - _profileCategories = new List(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order)); - ProfileCategories = new ReadOnlyCollection(_profileCategories); - + ProfileCategories = new ReadOnlyCollection(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order).ToList()); + _deviceService.LedsChanged += DeviceServiceOnLedsChanged; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled; inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp; - if (!_profileCategories.Any()) + if (!ProfileCategories.Any()) CreateDefaultProfileCategories(); UpdateModules(); } @@ -76,55 +75,52 @@ public void UpdateProfiles(double deltaTime) return; } - lock (_profileCategories) + // Iterate the children in reverse because the first category must be rendered last to end up on top + for (int i = ProfileCategories.Count - 1; i > -1; i--) { - // Iterate the children in reverse because the first category must be rendered last to end up on top - for (int i = _profileCategories.Count - 1; i > -1; i--) + ProfileCategory profileCategory = ProfileCategories[i]; + for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--) { - ProfileCategory profileCategory = _profileCategories[i]; - for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--) - { - ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; + ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; - // Process hotkeys that where pressed since this profile last updated - ProcessPendingKeyEvents(profileConfiguration); + // Process hotkeys that where pressed since this profile last updated + ProcessPendingKeyEvents(profileConfiguration); - bool shouldBeActive = profileConfiguration.ShouldBeActive(false); - if (shouldBeActive) - { - profileConfiguration.Update(); - shouldBeActive = profileConfiguration.ActivationConditionMet; - } + bool shouldBeActive = profileConfiguration.ShouldBeActive(false); + if (shouldBeActive) + { + profileConfiguration.Update(); + shouldBeActive = profileConfiguration.ActivationConditionMet; + } - try - { - // Make sure the profile is active or inactive according to the parameters above - if (shouldBeActive && profileConfiguration.Profile == null && profileConfiguration.BrokenState != "Failed to activate profile") - profileConfiguration.TryOrBreak(() => ActivateProfile(profileConfiguration), "Failed to activate profile"); - if (shouldBeActive && profileConfiguration.Profile != null && !profileConfiguration.Profile.ShouldDisplay) - profileConfiguration.Profile.ShouldDisplay = true; - else if (!shouldBeActive && profileConfiguration.Profile != null) - { - if (!profileConfiguration.FadeInAndOut) - DeactivateProfile(profileConfiguration); - else if (!profileConfiguration.Profile.ShouldDisplay && profileConfiguration.Profile.Opacity <= 0) - DeactivateProfile(profileConfiguration); - else if (profileConfiguration.Profile.Opacity > 0) - RequestDeactivation(profileConfiguration); - } - - profileConfiguration.Profile?.Update(deltaTime); - } - catch (Exception e) + try + { + // Make sure the profile is active or inactive according to the parameters above + if (shouldBeActive && profileConfiguration.Profile == null && profileConfiguration.BrokenState != "Failed to activate profile") + profileConfiguration.TryOrBreak(() => ActivateProfile(profileConfiguration), "Failed to activate profile"); + if (shouldBeActive && profileConfiguration.Profile != null && !profileConfiguration.Profile.ShouldDisplay) + profileConfiguration.Profile.ShouldDisplay = true; + else if (!shouldBeActive && profileConfiguration.Profile != null) { - _updateExceptions.Add(e); + if (!profileConfiguration.FadeInAndOut) + DeactivateProfile(profileConfiguration); + else if (!profileConfiguration.Profile.ShouldDisplay && profileConfiguration.Profile.Opacity <= 0) + DeactivateProfile(profileConfiguration); + else if (profileConfiguration.Profile.Opacity > 0) + RequestDeactivation(profileConfiguration); } + + profileConfiguration.Profile?.Update(deltaTime); + } + catch (Exception e) + { + _updateExceptions.Add(e); } } - - LogProfileUpdateExceptions(); - _pendingKeyboardEvents.Clear(); } + + LogProfileUpdateExceptions(); + _pendingKeyboardEvents.Clear(); } /// @@ -137,35 +133,32 @@ public void RenderProfiles(SKCanvas canvas) return; } - lock (_profileCategories) + // Iterate the children in reverse because the first category must be rendered last to end up on top + for (int i = ProfileCategories.Count - 1; i > -1; i--) { - // Iterate the children in reverse because the first category must be rendered last to end up on top - for (int i = _profileCategories.Count - 1; i > -1; i--) + ProfileCategory profileCategory = ProfileCategories[i]; + for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--) { - ProfileCategory profileCategory = _profileCategories[i]; - for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--) + try { - try - { - ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; - // Ensure all criteria are met before rendering - bool fadingOut = profileConfiguration.Profile?.ShouldDisplay == false && profileConfiguration.Profile?.Opacity > 0; - if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && (profileConfiguration.ActivationConditionMet || fadingOut)) - profileConfiguration.Profile?.Render(canvas, SKPointI.Empty, null); - } - catch (Exception e) - { - _renderExceptions.Add(e); - } + ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; + // Ensure all criteria are met before rendering + bool fadingOut = profileConfiguration.Profile?.ShouldDisplay == false && profileConfiguration.Profile?.Opacity > 0; + if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && (profileConfiguration.ActivationConditionMet || fadingOut)) + profileConfiguration.Profile?.Render(canvas, SKPointI.Empty, null); + } + catch (Exception e) + { + _renderExceptions.Add(e); } } - - LogProfileRenderExceptions(); } + + LogProfileRenderExceptions(); } /// - public ReadOnlyCollection ProfileCategories { get; } + public ReadOnlyCollection ProfileCategories { get; private set; } /// public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration) @@ -242,7 +235,7 @@ public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) if (addToTop) { profileCategory = new ProfileCategory(name, 1); - foreach (ProfileCategory category in _profileCategories) + foreach (ProfileCategory category in ProfileCategories) { category.Order++; category.Save(); @@ -250,11 +243,11 @@ public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) } else { - profileCategory = new ProfileCategory(name, _profileCategories.Count + 1); + profileCategory = new ProfileCategory(name, ProfileCategories.Count + 1); } _profileCategoryRepository.Add(profileCategory.Entity); - _profileCategories.Add(profileCategory); + ProfileCategories = new ReadOnlyCollection([..ProfileCategories, profileCategory]); OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory)); return profileCategory; @@ -266,7 +259,7 @@ public void DeleteProfileCategory(ProfileCategory profileCategory) foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations.ToList()) RemoveProfileConfiguration(profileConfiguration); - _profileCategories.Remove(profileCategory); + ProfileCategories = new ReadOnlyCollection(ProfileCategories.Where(c => c != profileCategory).ToList()); _profileCategoryRepository.Remove(profileCategory.Entity); OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory)); @@ -299,16 +292,14 @@ public void SaveProfileCategory(ProfileCategory profileCategory) { profileCategory.Save(); _profileCategoryRepository.SaveChanges(); - - lock (_profileCategories) - { - _profileCategories.Sort((a, b) => a.Order - b.Order); - } + ProfileCategories = new ReadOnlyCollection(ProfileCategories.OrderBy(c => c.Order).ToList()); } /// public void SaveProfile(Profile profile, bool includeChildren) { + Stopwatch sw = new(); + sw.Start(); _logger.Debug("Updating profile - Saving {Profile}", profile); profile.Save(); if (includeChildren) @@ -329,9 +320,17 @@ public void SaveProfile(Profile profile, bool includeChildren) .SelectMany(c => c.ProfileConfigurations) .FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id); if (localInstance == null) + { + sw.Stop(); + _logger.Debug("Updated profile - Saved {Profile} in {Time}ms", profile, sw.Elapsed.TotalMilliseconds); return; + } + DeactivateProfile(localInstance); ActivateProfile(localInstance); + + sw.Stop(); + _logger.Debug("Updated profile - Saved {Profile} in {Time}ms", profile, sw.Elapsed.TotalMilliseconds); } /// @@ -479,10 +478,7 @@ public void AdaptProfile(Profile profile) private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) { - lock (_profileCategories) - { - _pendingKeyboardEvents.Add(e); - } + _pendingKeyboardEvents.Add(e); } private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson) diff --git a/src/Artemis.Storage.Migrator/Program.cs b/src/Artemis.Storage.Migrator/Program.cs index 6ed140d3d..ba637973c 100644 --- a/src/Artemis.Storage.Migrator/Program.cs +++ b/src/Artemis.Storage.Migrator/Program.cs @@ -14,8 +14,6 @@ static void Main(string[] args) .WithoutThrowOnRegisteringDisposableTransient()); container.RegisterCore(); - container.Resolve().Database.EnsureCreated(); - } } \ No newline at end of file diff --git a/src/Artemis.Storage/ArtemisDbContext.cs b/src/Artemis.Storage/ArtemisDbContext.cs index 2063bbcf1..64ebbf963 100644 --- a/src/Artemis.Storage/ArtemisDbContext.cs +++ b/src/Artemis.Storage/ArtemisDbContext.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Text.Json; @@ -47,7 +46,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasConversion( v => JsonSerializer.Serialize(v, JsonSerializerOptions), v => JsonSerializer.Deserialize(v, JsonSerializerOptions) ?? new ProfileConfigurationEntity()); - + modelBuilder.Entity() .Property(e => e.Profile) .HasConversion( diff --git a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs index f47aa6bb8..f761dd98b 100644 --- a/src/Artemis.Storage/Entities/Profile/FolderEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/FolderEntity.cs @@ -1,6 +1,5 @@ using System; using Artemis.Storage.Entities.Profile.Abstract; -using LiteDB; namespace Artemis.Storage.Entities.Profile; @@ -11,8 +10,5 @@ public class FolderEntity : RenderElementEntity public bool IsExpanded { get; set; } public bool Suspended { get; set; } - [BsonRef("ProfileEntity")] - public ProfileEntity Profile { get; set; } = null!; - public Guid ProfileId { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs index 521550b34..d7e9dfe09 100644 --- a/src/Artemis.Storage/Entities/Profile/LayerEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/LayerEntity.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.AdaptionHints; -using LiteDB; namespace Artemis.Storage.Entities.Profile; @@ -25,8 +24,5 @@ public LayerEntity() public PropertyGroupEntity? TransformPropertyGroup { get; set; } public LayerBrushEntity? LayerBrush { get; set; } - [BsonRef("ProfileEntity")] - public ProfileEntity Profile { get; set; } = null!; - public Guid ProfileId { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/DeviceRepository.cs b/src/Artemis.Storage/Repositories/DeviceRepository.cs index d438da17a..28387fa6e 100644 --- a/src/Artemis.Storage/Repositories/DeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/DeviceRepository.cs @@ -31,9 +31,9 @@ public void Remove(DeviceEntity deviceEntity) return _dbContext.Devices.FirstOrDefault(d => d.Id == id); } - public List GetAll() + public IEnumerable GetAll() { - return _dbContext.Devices.ToList(); + return _dbContext.Devices; } public void SaveChanges() diff --git a/src/Artemis.Storage/Repositories/EntryRepository.cs b/src/Artemis.Storage/Repositories/EntryRepository.cs index 8f719eb52..e378ace28 100644 --- a/src/Artemis.Storage/Repositories/EntryRepository.cs +++ b/src/Artemis.Storage/Repositories/EntryRepository.cs @@ -37,9 +37,9 @@ public void Remove(EntryEntity entryEntity) return _dbContext.Entries.FirstOrDefault(s => s.EntryId == entryId); } - public List GetAll() + public IEnumerable GetAll() { - return _dbContext.Entries.ToList(); + return _dbContext.Entries; } public void SaveChanges() diff --git a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs index 172f23e61..960f61d66 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs @@ -8,6 +8,6 @@ public interface IDeviceRepository : IRepository void Add(DeviceEntity deviceEntity); void Remove(DeviceEntity deviceEntity); DeviceEntity? Get(string id); - List GetAll(); + IEnumerable GetAll(); void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs index 0e16c6dde..f1146de9f 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs @@ -10,6 +10,6 @@ public interface IEntryRepository : IRepository void Remove(EntryEntity entryEntity); EntryEntity? Get(Guid id); EntryEntity? GetByEntryId(long entryId); - List GetAll(); + IEnumerable GetAll(); void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs index d6d766344..87e3e48e4 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs @@ -8,7 +8,6 @@ public interface IPluginRepository : IRepository void AddPlugin(PluginEntity pluginEntity); PluginEntity? GetPluginByGuid(Guid pluginGuid); void AddSetting(PluginSettingEntity pluginSettingEntity); - PluginSettingEntity? GetSettingByGuid(Guid pluginGuid); PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid); void RemoveSettings(Guid pluginGuid); void SaveChanges(); diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs index 190530bbc..8c53d776c 100644 --- a/src/Artemis.Storage/Repositories/PluginRepository.cs +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -2,6 +2,7 @@ using System.Linq; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Repositories; @@ -22,7 +23,7 @@ public void AddPlugin(PluginEntity pluginEntity) public PluginEntity? GetPluginByGuid(Guid pluginGuid) { - return _dbContext.Plugins.FirstOrDefault(p => p.Id == pluginGuid); + return _dbContext.Plugins.Include(p => p.Features).FirstOrDefault(p => p.Id == pluginGuid); } public void AddSetting(PluginSettingEntity pluginSettingEntity) @@ -31,11 +32,6 @@ public void AddSetting(PluginSettingEntity pluginSettingEntity) SaveChanges(); } - public PluginSettingEntity? GetSettingByGuid(Guid pluginGuid) - { - return _dbContext.PluginSettings.FirstOrDefault(p => p.PluginGuid == pluginGuid); - } - public PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid) { return _dbContext.PluginSettings.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid); diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs index 7b612a927..d7c255363 100644 --- a/src/Artemis.Storage/StorageManager.cs +++ b/src/Artemis.Storage/StorageManager.cs @@ -1,12 +1,13 @@ using System; using System.IO; using System.Linq; -using LiteDB; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage; public static class StorageManager { + private static bool _ranMigrations; private static bool _inUse; /// @@ -19,7 +20,7 @@ public static void CreateBackup(string dataFolder) if (_inUse) throw new Exception("Storage is already in use, can't backup now."); - string database = Path.Combine(dataFolder, "database.db"); + string database = Path.Combine(dataFolder, "artemis.db"); if (!File.Exists(database)) return; @@ -36,35 +37,20 @@ public static void CreateBackup(string dataFolder) oldest.Delete(); } - File.Copy(database, Path.Combine(backupFolder, $"database-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.db")); + File.Copy(database, Path.Combine(backupFolder, $"artemis-{DateTime.Now:yyyy-dd-M--HH-mm-ss}.db")); } - - /// - /// Creates the LiteRepository that will be managed by dependency injection - /// - /// The Artemis data folder - public static LiteRepository CreateRepository(string dataFolder) + + public static ArtemisDbContext CreateDbContext(string dataFolder) { - if (_inUse) - throw new Exception("Storage is already in use, use dependency injection to get the repository."); + _inUse = true; - try - { - _inUse = true; - return new LiteRepository($"FileName={Path.Combine(dataFolder, "database.db")}"); - } - catch (LiteException e) - { - // I don't like this way of error reporting, now I need to use reflection if I want a meaningful error message - throw new Exception($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e); - } - } + ArtemisDbContext dbContext = new() {DataFolder = dataFolder}; + if (_ranMigrations) + return dbContext; - public static ArtemisDbContext CreateDbContext(string dataFolder) - { - return new ArtemisDbContext() - { - DataFolder = dataFolder - }; + dbContext.Database.Migrate(); + _ranMigrations = true; + + return dbContext; } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/AboutTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/AboutTabView.axaml index e29210098..70b2bc390 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/AboutTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/AboutTabView.axaml @@ -174,16 +174,15 @@ Avalonia DryIoc + Entity Framework Core FluentAvalonia EmbedIO Humanizer - LiteDB McMaster.NETCore.Plugins - Newtonsoft.Json RGB.NET Serilog SkiaSharp - Unclassified.NetRevisionTask + SQLite @@ -192,6 +191,9 @@ https://github.com/dadhi/DryIoc + + https://learn.microsoft.com/en-us/ef/core/ + https://github.com/amwx/FluentAvalonia @@ -201,15 +203,9 @@ https://github.com/Humanizr/Humanizer - - https://www.litedb.org/ - https://github.com/natemcmaster/DotNetCorePlugins - - https://www.newtonsoft.com/json - https://github.com/DarthAffe/RGB.NET @@ -218,9 +214,9 @@ https://github.com/mono/SkiaSharp - - - https://unclassified.software/en/apps/netrevisiontask + + + https://www.sqlite.org/ diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs index 6e3ee0d0b..87bb9c949 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabViewModel.cs @@ -162,7 +162,7 @@ public RenderSettingViewModel? SelectedTargetFrameRate public PluginSetting ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information); public PluginSetting CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software"); - public PluginSetting CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25); + public PluginSetting CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.5); public PluginSetting CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30); public PluginSetting WebServerEnabled => _settingsService.GetSetting("WebServer.Enabled", true); public PluginSetting WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696); diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs index aa79c5e75..afa278f98 100644 --- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs @@ -122,6 +122,7 @@ private async Task ExecuteConfirm() ProfileConfiguration.FadeInAndOut = FadeInAndOut; await SaveIcon(); + ProfileConfiguration.Save(); _profileService.SaveProfileCategory(_profileCategory); Close(ProfileConfiguration); diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs index 47bff74f0..be21aeb89 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceDeviceViewModel.cs @@ -97,7 +97,7 @@ private bool Fits(float x, float y, bool ignoreOverlap) if (x < 0 || y < 0) return false; - double maxTextureSize = 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value; + double maxTextureSize = 4096 / _settingsService.GetSetting("Core.RenderScale", 0.5).Value; if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize) return false; diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index 932313e96..248ca19de 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -87,7 +87,7 @@ public SurfaceEditorViewModel(ICoreService coreService, public ReactiveCommand SendToBack { get; } public ReactiveCommand SendBackward { get; } - public double MaxTextureSize => 4096 / _settingsService.GetSetting("Core.RenderScale", 0.25).Value; + public double MaxTextureSize => 4096 / _settingsService.GetSetting("Core.RenderScale", 0.5).Value; public void UpdateSelection(List devices, bool expand, bool invert) { diff --git a/src/Artemis.WebClient.Workshop/Repositories/AuthenticationRepository.cs b/src/Artemis.WebClient.Workshop/Repositories/AuthenticationRepository.cs index 9a3021c1f..8f96a314c 100644 --- a/src/Artemis.WebClient.Workshop/Repositories/AuthenticationRepository.cs +++ b/src/Artemis.WebClient.Workshop/Repositories/AuthenticationRepository.cs @@ -1,31 +1,30 @@ -using Artemis.WebClient.Workshop.Entities; -using LiteDB; +using Artemis.Core; +using Artemis.Core.Services; namespace Artemis.WebClient.Workshop.Repositories; internal class AuthenticationRepository : IAuthenticationRepository { - private readonly LiteRepository _repository; + private readonly PluginSetting _refreshToken; - public AuthenticationRepository(LiteRepository repository) + public AuthenticationRepository(ISettingsService settingsService) { - _repository = repository; - _repository.Database.GetCollection().EnsureIndex(s => s.RefreshToken); + // Of course anyone can grab these indirectly, but that goes for whatever we do. + // ISettingsService is a protected service so we at least don't make it very straightforward. + _refreshToken = settingsService.GetSetting("Workshop.RefreshToken"); } /// public void SetRefreshToken(string? refreshToken) { - _repository.Database.GetCollection().DeleteAll(); - - if (refreshToken != null) - _repository.Insert(new RefreshTokenEntity {RefreshToken = refreshToken}); + _refreshToken.Value = refreshToken; + _refreshToken.Save(); } /// public string? GetRefreshToken() { - return _repository.Query().FirstOrDefault()?.RefreshToken; + return _refreshToken.Value; } } diff --git a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs index 6d2d36e9a..3da255d12 100644 --- a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs @@ -171,7 +171,12 @@ public void RemoveInstalledEntry(InstalledEntry installedEntry) public void SaveInstalledEntry(InstalledEntry entry) { entry.Save(); - _entryRepository.SaveChanges(); + + // Upsert for plebs + if (entry.Entity.Id == Guid.Empty) + _entryRepository.Add(entry.Entity); + else + _entryRepository.SaveChanges(); } /// From d4e4d52f8435535d63301c1d619e46b47dfa92dd Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 9 Mar 2024 22:47:36 +0100 Subject: [PATCH 04/14] Storage - Added LiteDB to SQLite migration --- .../Services/Storage/ProfileService.cs | 1 - .../Artemis.Storage.Migrator.csproj | 1 + .../Entities/General/QueuedActionEntity.cs | 15 ++ .../Legacy/Entities/General/ReleaseEntity.cs | 19 ++ .../General/ScriptConfigurationEntity.cs | 10 + .../Legacy/Entities/Plugins/PluginEntity.cs | 45 ++++ .../Entities/Plugins/PluginSettingEntity.cs | 24 ++ .../Profile/Abstract/RenderElementEntity.cs | 14 ++ .../CategoryAdaptionHintEntity.cs | 10 + .../AdaptionHints/DeviceAdaptionHintEntity.cs | 10 + .../AdaptionHints/IAdaptionHintEntity.cs | 9 + .../KeyboardSectionAdaptionHintEntity.cs | 6 + .../SingleLedAdaptionHintEntity.cs | 10 + .../Conditions/AlwaysOnConditionEntity.cs | 3 + .../Conditions/EventConditionEntity.cs | 12 + .../Profile/Conditions/IConditionEntity.cs | 9 + .../Conditions/PlayOnceConditionEntity.cs | 3 + .../Conditions/StaticConditionEntity.cs | 10 + .../Profile/DataBindings/DataBindingEntity.cs | 9 + .../Entities/Profile/DataModelPathEntity.cs | 8 + .../Legacy/Entities/Profile/FolderEntity.cs | 17 ++ .../Legacy/Entities/Profile/KeyframeEntity.cs | 9 + .../Entities/Profile/LayerBrushEntity.cs | 9 + .../Entities/Profile/LayerEffectEntity.cs | 12 + .../Legacy/Entities/Profile/LayerEntity.cs | 30 +++ .../Legacy/Entities/Profile/LedEntity.cs | 36 +++ .../Profile/Nodes/NodeConnectionEntity.cs | 29 +++ .../Entities/Profile/Nodes/NodeEntity.cs | 38 +++ .../Profile/Nodes/NodePinCollectionEntity.cs | 19 ++ .../Profile/Nodes/NodeScriptEntity.cs | 16 ++ .../Entities/Profile/ProfileCategoryEntity.cs | 78 ++++++ .../Profile/ProfileConfigurationEntity.cs | 29 +++ .../ProfileConfigurationHotkeyEntity.cs | 7 + .../Legacy/Entities/Profile/ProfileEntity.cs | 32 +++ .../Legacy/Entities/Profile/PropertyEntity.cs | 13 + .../Entities/Profile/PropertyGroupEntity.cs | 8 + .../Legacy/Entities/Profile/TimelineEntity.cs | 8 + .../Legacy/Entities/Surface/DeviceEntity.cs | 78 ++++++ .../Legacy/Entities/Workshop/EntryEntity.cs | 35 +++ .../Legacy/Migrations/IProfileMigration.cs | 9 + .../Legacy/Migrations/IStorageMigration.cs | 9 + .../Migrations/Storage/M0020AvaloniaReset.cs | 17 ++ .../Migrations/Storage/M0021GradientNodes.cs | 87 +++++++ .../Storage/M0022TransitionNodes.cs | 84 +++++++ .../Storage/M0023LayoutProviders.cs | 33 +++ .../Migrations/Storage/M0024NodeProviders.cs | 105 ++++++++ .../M0025NodeProvidersProfileConfig.cs | 45 ++++ .../Migrations/Storage/M0026NodeStorage.cs | 227 ++++++++++++++++++ .../Migrations/Storage/M0027Namespace.cs | 45 ++++ .../Legacy/StorageMigrationService.cs | 34 +++ src/Artemis.Storage.Migrator/Program.cs | 129 +++++++++- src/Artemis.Storage/Artemis.Storage.csproj | 1 - .../Profile/ProfileConfigurationEntity.cs | 2 - .../ProfileConfigurationIcon.axaml.cs | 29 ++- src/Directory.Packages.props | 1 - 55 files changed, 1569 insertions(+), 19 deletions(-) create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/General/QueuedActionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/General/ReleaseEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/General/ScriptConfigurationEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginSettingEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Abstract/RenderElementEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/EventConditionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/IConditionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataModelPathEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/FolderEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/KeyframeEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerBrushEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEffectEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LedEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileCategoryEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyGroupEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/TimelineEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Surface/DeviceEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Workshop/EntryEntity.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/IProfileMigration.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/IStorageMigration.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0020AvaloniaReset.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0021GradientNodes.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0022TransitionNodes.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0023LayoutProviders.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0024NodeProviders.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0026NodeStorage.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0027Namespace.cs create mode 100644 src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 7e0112af7..423f50797 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -434,7 +434,6 @@ public async Task ImportProfile(Stream archiveStream, Prof } // A new GUID will be given on save - configurationEntity.FileIconId = Guid.Empty; ProfileConfiguration profileConfiguration = new(category, containerEntity); if (nameAffix != null) profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}"; diff --git a/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj b/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj index f35f98a79..dbc3a660d 100644 --- a/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj +++ b/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj @@ -13,6 +13,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/General/QueuedActionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/General/QueuedActionEntity.cs new file mode 100644 index 000000000..8244c57ec --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/General/QueuedActionEntity.cs @@ -0,0 +1,15 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.General; + +public class QueuedActionEntity +{ + public QueuedActionEntity() + { + Parameters = new Dictionary(); + } + + public Guid Id { get; set; } + public string Type { get; set; } = string.Empty; + public DateTimeOffset CreatedAt { get; set; } + + public Dictionary Parameters { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/General/ReleaseEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/General/ReleaseEntity.cs new file mode 100644 index 000000000..a0d9795bf --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/General/ReleaseEntity.cs @@ -0,0 +1,19 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.General; + +public class ReleaseEntity +{ + public Guid Id { get; set; } + + public string Version { get; set; } = string.Empty; + public DateTimeOffset? InstalledAt { get; set; } + + public Storage.Entities.General.ReleaseEntity Migrate() + { + return new Storage.Entities.General.ReleaseEntity() + { + Id = Id, + Version = Version, + InstalledAt = InstalledAt ?? DateTimeOffset.Now + }; + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/General/ScriptConfigurationEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/General/ScriptConfigurationEntity.cs new file mode 100644 index 000000000..4c1743fb6 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/General/ScriptConfigurationEntity.cs @@ -0,0 +1,10 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.General; + +public class ScriptConfigurationEntity +{ + public Guid Id { get; set; } + + public string Name { get; set; } = string.Empty; + public string ScriptingProviderId { get; set; } = string.Empty; + public string? ScriptContent { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginEntity.cs new file mode 100644 index 000000000..779b42fff --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginEntity.cs @@ -0,0 +1,45 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Plugins; + +/// +/// Represents the configuration of a plugin, each plugin has one configuration +/// +public class PluginEntity +{ + public PluginEntity() + { + Features = new List(); + } + + public Guid Id { get; set; } + public bool IsEnabled { get; set; } + + public List Features { get; set; } + + public Artemis.Storage.Entities.Plugins.PluginEntity Migrate() + { + return new Artemis.Storage.Entities.Plugins.PluginEntity() + { + Id = Id, + IsEnabled = IsEnabled, + Features = Features.Select(f => f.Migrate()).ToList() + }; + } +} + +/// +/// Represents the configuration of a plugin feature, each feature has one configuration +/// +public class PluginFeatureEntity +{ + public string Type { get; set; } = string.Empty; + public bool IsEnabled { get; set; } + + public Artemis.Storage.Entities.Plugins.PluginFeatureEntity Migrate() + { + return new Artemis.Storage.Entities.Plugins.PluginFeatureEntity() + { + Type = Type, + IsEnabled = IsEnabled + }; + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginSettingEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginSettingEntity.cs new file mode 100644 index 000000000..2d10b80e2 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginSettingEntity.cs @@ -0,0 +1,24 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Plugins; + +/// +/// Represents the setting of a plugin, a plugin can have multiple settings +/// +public class PluginSettingEntity +{ + public Guid Id { get; set; } + public Guid PluginGuid { get; set; } + + public string Name { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; + + public Artemis.Storage.Entities.Plugins.PluginSettingEntity Migrate() + { + return new Storage.Entities.Plugins.PluginSettingEntity + { + Id = Id, + PluginGuid = PluginGuid, + Name = Name, + Value = Value + }; + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Abstract/RenderElementEntity.cs new file mode 100644 index 000000000..98f01a41e --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Abstract/RenderElementEntity.cs @@ -0,0 +1,14 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract; + +public abstract class RenderElementEntity +{ + public Guid Id { get; set; } + public Guid ParentId { get; set; } + + public List LayerEffects { get; set; } = new(); + + public IConditionEntity? DisplayCondition { get; set; } + public TimelineEntity? Timeline { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs new file mode 100644 index 000000000..3576c5cde --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs @@ -0,0 +1,10 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; + +public class CategoryAdaptionHintEntity : IAdaptionHintEntity +{ + public int Category { get; set; } + + public bool LimitAmount { get; set; } + public int Skip { get; set; } + public int Amount { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs new file mode 100644 index 000000000..f7a078867 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs @@ -0,0 +1,10 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; + +public class DeviceAdaptionHintEntity : IAdaptionHintEntity +{ + public int DeviceType { get; set; } + + public bool LimitAmount { get; set; } + public int Skip { get; set; } + public int Amount { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs new file mode 100644 index 000000000..9ff03f4bd --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; + +[JsonDerivedType(typeof(CategoryAdaptionHintEntity), "Category")] +[JsonDerivedType(typeof(DeviceAdaptionHintEntity), "Device")] +[JsonDerivedType(typeof(KeyboardSectionAdaptionHintEntity), "KeyboardSection")] +[JsonDerivedType(typeof(SingleLedAdaptionHintEntity), "SingleLed")] +public interface IAdaptionHintEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs new file mode 100644 index 000000000..075f3ad9a --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs @@ -0,0 +1,6 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; + +public class KeyboardSectionAdaptionHintEntity : IAdaptionHintEntity +{ + public int Section { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs new file mode 100644 index 000000000..98d192595 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs @@ -0,0 +1,10 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; + +public class SingleLedAdaptionHintEntity : IAdaptionHintEntity +{ + public int LedId { get; set; } + + public bool LimitAmount { get; set; } + public int Skip { get; set; } + public int Amount { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs new file mode 100644 index 000000000..e644c4331 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs @@ -0,0 +1,3 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; + +public class AlwaysOnConditionEntity : IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/EventConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/EventConditionEntity.cs new file mode 100644 index 000000000..2fea241b0 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/EventConditionEntity.cs @@ -0,0 +1,12 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; + +public class EventConditionEntity : IConditionEntity +{ + public int TriggerMode { get; set; } + public int OverlapMode { get; set; } + public int ToggleOffMode { get; set; } + public DataModelPathEntity? EventPath { get; set; } + public NodeScriptEntity? Script { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/IConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/IConditionEntity.cs new file mode 100644 index 000000000..bc78042aa --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/IConditionEntity.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; + +[JsonDerivedType(typeof(AlwaysOnConditionEntity), "AlwaysOn")] +[JsonDerivedType(typeof(EventConditionEntity), "Event")] +[JsonDerivedType(typeof(PlayOnceConditionEntity), "PlayOnce")] +[JsonDerivedType(typeof(StaticConditionEntity), "Static")] +public interface IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs new file mode 100644 index 000000000..82fb095b1 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs @@ -0,0 +1,3 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; + +public class PlayOnceConditionEntity : IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs new file mode 100644 index 000000000..065bd92bd --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs @@ -0,0 +1,10 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; + +public class StaticConditionEntity : IConditionEntity +{ + public int PlayMode { get; set; } + public int StopMode { get; set; } + public NodeScriptEntity? Script { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs new file mode 100644 index 000000000..74aa3851e --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -0,0 +1,9 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.DataBindings; + +public class DataBindingEntity +{ + public bool IsEnabled { get; set; } + public NodeScriptEntity? NodeScript { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataModelPathEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataModelPathEntity.cs new file mode 100644 index 000000000..0783e603f --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataModelPathEntity.cs @@ -0,0 +1,8 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class DataModelPathEntity +{ + public string Path { get; set; } = string.Empty; + public string? DataModelId { get; set; } + public string? Type { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/FolderEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/FolderEntity.cs new file mode 100644 index 000000000..ef181ff5d --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/FolderEntity.cs @@ -0,0 +1,17 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract; +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class FolderEntity : RenderElementEntity +{ + public int Order { get; set; } + public string? Name { get; set; } + public bool IsExpanded { get; set; } + public bool Suspended { get; set; } + + [BsonRef("ProfileEntity")] + public ProfileEntity Profile { get; set; } = null!; + + public Guid ProfileId { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/KeyframeEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/KeyframeEntity.cs new file mode 100644 index 000000000..fae285769 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/KeyframeEntity.cs @@ -0,0 +1,9 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class KeyframeEntity +{ + public TimeSpan Position { get; set; } + public int Timeline { get; set; } + public string Value { get; set; } = string.Empty; + public int EasingFunction { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerBrushEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerBrushEntity.cs new file mode 100644 index 000000000..f0579e9ac --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerBrushEntity.cs @@ -0,0 +1,9 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class LayerBrushEntity +{ + public string ProviderId { get; set; } = string.Empty; + public string BrushType { get; set; } = string.Empty; + + public PropertyGroupEntity? PropertyGroup { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEffectEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEffectEntity.cs new file mode 100644 index 000000000..b3b8e5bdb --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEffectEntity.cs @@ -0,0 +1,12 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class LayerEffectEntity +{ + public string ProviderId { get; set; } = string.Empty; + public string EffectType { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public bool HasBeenRenamed { get; set; } + public int Order { get; set; } + + public PropertyGroupEntity? PropertyGroup { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEntity.cs new file mode 100644 index 000000000..57d5ea555 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEntity.cs @@ -0,0 +1,30 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract; +using Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class LayerEntity : RenderElementEntity +{ + public LayerEntity() + { + Leds = new List(); + AdaptionHints = new List(); + } + + public int Order { get; set; } + public string? Name { get; set; } + public bool Suspended { get; set; } + + public List Leds { get; set; } + public List AdaptionHints { get; set; } + + public PropertyGroupEntity? GeneralPropertyGroup { get; set; } + public PropertyGroupEntity? TransformPropertyGroup { get; set; } + public LayerBrushEntity? LayerBrush { get; set; } + + [BsonRef("ProfileEntity")] + public ProfileEntity Profile { get; set; } = null!; + + public Guid ProfileId { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LedEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LedEntity.cs new file mode 100644 index 000000000..b456a138c --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LedEntity.cs @@ -0,0 +1,36 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class LedEntity +{ + public string LedName { get; set; } = string.Empty; + public string DeviceIdentifier { get; set; } = string.Empty; + + public int? PhysicalLayout { get; set; } + + #region LedEntityEqualityComparer + + private sealed class LedEntityEqualityComparer : IEqualityComparer + { + public bool Equals(LedEntity? x, LedEntity? y) + { + if (ReferenceEquals(x, y)) + return true; + if (ReferenceEquals(x, null)) + return false; + if (ReferenceEquals(y, null)) + return false; + if (x.GetType() != y.GetType()) + return false; + return x.LedName == y.LedName && x.DeviceIdentifier == y.DeviceIdentifier && x.PhysicalLayout == y.PhysicalLayout; + } + + public int GetHashCode(LedEntity obj) + { + return HashCode.Combine(obj.LedName, obj.DeviceIdentifier, obj.PhysicalLayout); + } + } + + public static IEqualityComparer LedEntityComparer { get; } = new LedEntityEqualityComparer(); + + #endregion +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs new file mode 100644 index 000000000..d256cce6b --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs @@ -0,0 +1,29 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +public class NodeConnectionEntity +{ + public NodeConnectionEntity() + { + } + + public NodeConnectionEntity(NodeConnectionEntity nodeConnectionEntity) + { + SourceType = nodeConnectionEntity.SourceType; + SourceNode = nodeConnectionEntity.SourceNode; + TargetNode = nodeConnectionEntity.TargetNode; + SourcePinCollectionId = nodeConnectionEntity.SourcePinCollectionId; + SourcePinId = nodeConnectionEntity.SourcePinId; + TargetType = nodeConnectionEntity.TargetType; + TargetPinCollectionId = nodeConnectionEntity.TargetPinCollectionId; + TargetPinId = nodeConnectionEntity.TargetPinId; + } + + public string SourceType { get; set; } = string.Empty; + public Guid SourceNode { get; set; } + public Guid TargetNode { get; set; } + public int SourcePinCollectionId { get; set; } + public int SourcePinId { get; set; } + public string TargetType { get; set; } = string.Empty; + public int TargetPinCollectionId { get; set; } + public int TargetPinId { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeEntity.cs new file mode 100644 index 000000000..b25e9b0f7 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeEntity.cs @@ -0,0 +1,38 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +public class NodeEntity +{ + public NodeEntity() + { + PinCollections = new List(); + } + + public NodeEntity(NodeEntity nodeEntity) + { + Id = nodeEntity.Id; + Type = nodeEntity.Type; + ProviderId = nodeEntity.ProviderId; + + Name = nodeEntity.Name; + Description = nodeEntity.Description; + IsExitNode = nodeEntity.IsExitNode; + X = nodeEntity.X; + Y = nodeEntity.Y; + Storage = nodeEntity.Storage; + + PinCollections = nodeEntity.PinCollections.Select(p => new NodePinCollectionEntity(p)).ToList(); + } + + public Guid Id { get; set; } + public string Type { get; set; } = string.Empty; + public string ProviderId { get; set; } = string.Empty; + + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public bool IsExitNode { get; set; } + public double X { get; set; } + public double Y { get; set; } + public string Storage { get; set; } = string.Empty; + + public List PinCollections { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs new file mode 100644 index 000000000..cdec98e6a --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs @@ -0,0 +1,19 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +public class NodePinCollectionEntity +{ + public NodePinCollectionEntity() + { + } + + public NodePinCollectionEntity(NodePinCollectionEntity nodePinCollectionEntity) + { + Id = nodePinCollectionEntity.Id; + Direction = nodePinCollectionEntity.Direction; + Amount = nodePinCollectionEntity.Amount; + } + + public int Id { get; set; } + public int Direction { set; get; } + public int Amount { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs new file mode 100644 index 000000000..5afeb39df --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs @@ -0,0 +1,16 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +public class NodeScriptEntity +{ + public NodeScriptEntity() + { + Nodes = new List(); + Connections = new List(); + } + + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + + public List Nodes { get; set; } + public List Connections { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileCategoryEntity.cs new file mode 100644 index 000000000..0323db70d --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileCategoryEntity.cs @@ -0,0 +1,78 @@ +using Artemis.Core; +using Artemis.Storage.Entities.Profile; +using LiteDB; +using Serilog; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class ProfileCategoryEntity +{ + public Guid Id { get; set; } + + public string Name { get; set; } = string.Empty; + public bool IsCollapsed { get; set; } + public bool IsSuspended { get; set; } + public int Order { get; set; } + + public List ProfileConfigurations { get; set; } = new(); + + public Storage.Entities.Profile.ProfileCategoryEntity Migrate(ILogger logger, List legacyProfiles, ILiteStorage profileIcons) + { + Storage.Entities.Profile.ProfileCategoryEntity category = new() + { + Id = Id, + Name = Name, + IsCollapsed = IsCollapsed, + IsSuspended = IsSuspended, + Order = Order + }; + + foreach (ProfileConfigurationEntity legacyProfileConfiguration in ProfileConfigurations) + { + // Find the profile + ProfileEntity? legacyProfile = legacyProfiles.FirstOrDefault(p => p.Id == legacyProfileConfiguration.ProfileId); + if (legacyProfile == null) + { + logger.Information("Profile not found for profile configuration {ProfileId}", legacyProfileConfiguration.ProfileId); + continue; + } + + // Clone to the new format via JSON, as both are serializable + string profileJson = CoreJson.Serialize(legacyProfile); + string configJson = CoreJson.Serialize(legacyProfileConfiguration); + Storage.Entities.Profile.ProfileEntity? profile = CoreJson.Deserialize(profileJson); + Storage.Entities.Profile.ProfileConfigurationEntity? config = CoreJson.Deserialize(configJson); + + if (profile == null) + { + logger.Information("Failed to deserialize profile JSON for profile configuration {ProfileId}", legacyProfileConfiguration.ProfileId); + continue; + } + + if (config == null) + { + logger.Information("Failed to deserialize profile configuration JSON for profile configuration {ProfileId}", legacyProfileConfiguration.ProfileId); + continue; + } + + // Add a container + ProfileContainerEntity container = new() + { + Profile = profile, + ProfileConfiguration = config, + }; + + // If available, download the profile icon + if (profileIcons.Exists(legacyProfileConfiguration.FileIconId)) + { + using MemoryStream memoryStream = new(); + profileIcons.Download(legacyProfileConfiguration.FileIconId, memoryStream); + container.Icon = memoryStream.ToArray(); + } + + category.ProfileConfigurations.Add(container); + } + + return category; + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationEntity.cs new file mode 100644 index 000000000..89f68fe1d --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationEntity.cs @@ -0,0 +1,29 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class ProfileConfigurationEntity +{ + public string Name { get; set; } = string.Empty; + public string? MaterialIcon { get; set; } + public Guid FileIconId { get; set; } + public int IconType { get; set; } + public bool IconFill { get; set; } + public int Order { get; set; } + + public bool IsSuspended { get; set; } + public int ActivationBehaviour { get; set; } + public NodeScriptEntity? ActivationCondition { get; set; } + + public int HotkeyMode { get; set; } + public ProfileConfigurationHotkeyEntity? EnableHotkey { get; set; } + public ProfileConfigurationHotkeyEntity? DisableHotkey { get; set; } + + public string? ModuleId { get; set; } + + public Guid ProfileCategoryId { get; set; } + public Guid ProfileId { get; set; } + + public bool FadeInAndOut { get; set; } + public int Version { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs new file mode 100644 index 000000000..a0572cc91 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs @@ -0,0 +1,7 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class ProfileConfigurationHotkeyEntity +{ + public int? Key { get; set; } + public int? Modifiers { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileEntity.cs new file mode 100644 index 000000000..cbfde4b61 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileEntity.cs @@ -0,0 +1,32 @@ +using Artemis.Storage.Migrator.Legacy.Entities.General; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class ProfileEntity +{ + public ProfileEntity() + { + Folders = new List(); + Layers = new List(); + ScriptConfigurations = new List(); + } + + public Guid Id { get; set; } + + public string Name { get; set; } = string.Empty; + public bool IsFreshImport { get; set; } + + public List Folders { get; set; } + public List Layers { get; set; } + public List ScriptConfigurations { get; set; } + + public void UpdateGuid(Guid guid) + { + Guid oldGuid = Id; + Id = guid; + + FolderEntity? rootFolder = Folders.FirstOrDefault(f => f.ParentId == oldGuid); + if (rootFolder != null) + rootFolder.ParentId = Id; + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyEntity.cs new file mode 100644 index 000000000..1a0a5d4b6 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyEntity.cs @@ -0,0 +1,13 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile.DataBindings; + +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class PropertyEntity +{ + public string Identifier { get; set; } = string.Empty; + public string Value { get; set; } = string.Empty; + public bool KeyframesEnabled { get; set; } + + public DataBindingEntity? DataBinding { get; set; } + public List KeyframeEntities { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyGroupEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyGroupEntity.cs new file mode 100644 index 000000000..8097d36ba --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyGroupEntity.cs @@ -0,0 +1,8 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class PropertyGroupEntity +{ + public string Identifier { get; set; } = string.Empty; + public List Properties { get; set; } = new(); + public List PropertyGroups { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/TimelineEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/TimelineEntity.cs new file mode 100644 index 000000000..a9401abac --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/TimelineEntity.cs @@ -0,0 +1,8 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; + +public class TimelineEntity +{ + public TimeSpan StartSegmentLength { get; set; } + public TimeSpan MainSegmentLength { get; set; } + public TimeSpan EndSegmentLength { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Surface/DeviceEntity.cs new file mode 100644 index 000000000..52d86fbcc --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Surface/DeviceEntity.cs @@ -0,0 +1,78 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Surface; + +public class DeviceEntity +{ + public DeviceEntity() + { + InputIdentifiers = new List(); + InputMappings = new List(); + Categories = new List(); + } + + public string Id { get; set; } = string.Empty; + public string DeviceProvider { get; set; } = string.Empty; + public float X { get; set; } + public float Y { get; set; } + public float Rotation { get; set; } + public float Scale { get; set; } + public int ZIndex { get; set; } + public float RedScale { get; set; } + public float GreenScale { get; set; } + public float BlueScale { get; set; } + public bool IsEnabled { get; set; } + + public int PhysicalLayout { get; set; } + public string? LogicalLayout { get; set; } + public string? LayoutType { get; set; } + public string? LayoutParameter { get; set; } + + public List InputIdentifiers { get; set; } + public List InputMappings { get; set; } + public List Categories { get; set; } + + public Storage.Entities.Surface.DeviceEntity Migrate() + { + // All properties match, return a copy + return new Storage.Entities.Surface.DeviceEntity() + { + Id = Id, + DeviceProvider = DeviceProvider, + X = X, + Y = Y, + Rotation = Rotation, + Scale = Scale, + ZIndex = ZIndex, + RedScale = RedScale, + GreenScale = GreenScale, + BlueScale = BlueScale, + IsEnabled = IsEnabled, + PhysicalLayout = PhysicalLayout, + LogicalLayout = LogicalLayout, + LayoutType = LayoutType, + LayoutParameter = LayoutParameter, + InputIdentifiers = InputIdentifiers.Select(i => new Storage.Entities.Surface.DeviceInputIdentifierEntity + { + InputProvider = i.InputProvider, + Identifier = i.Identifier.ToString() ?? string.Empty + }).ToList(), + InputMappings = InputMappings.Select(i => new Storage.Entities.Surface.InputMappingEntity + { + OriginalLedId = i.OriginalLedId, + MappedLedId = i.MappedLedId + }).ToList(), + Categories = Categories + }; + } +} + +public class InputMappingEntity +{ + public int OriginalLedId { get; set; } + public int MappedLedId { get; set; } +} + +public class DeviceInputIdentifierEntity +{ + public string InputProvider { get; set; } = string.Empty; + public object Identifier { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Workshop/EntryEntity.cs new file mode 100644 index 000000000..c5fea30d2 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Entities/Workshop/EntryEntity.cs @@ -0,0 +1,35 @@ +namespace Artemis.Storage.Migrator.Legacy.Entities.Workshop; + +public class EntryEntity +{ + public Guid Id { get; set; } + + public long EntryId { get; set; } + public int EntryType { get; set; } + + public string Author { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + + public long ReleaseId { get; set; } + public string ReleaseVersion { get; set; } = string.Empty; + public DateTimeOffset InstalledAt { get; set; } + + public Dictionary? Metadata { get; set; } + + public Storage.Entities.Workshop.EntryEntity Migrate() + { + // Create a copy + return new Storage.Entities.Workshop.EntryEntity() + { + Id = Id, + EntryId = EntryId, + EntryType = EntryType, + Author = Author, + Name = Name, + ReleaseId = ReleaseId, + ReleaseVersion = ReleaseVersion, + InstalledAt = InstalledAt, + Metadata = Metadata ?? new Dictionary() + }; + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/IProfileMigration.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/IProfileMigration.cs new file mode 100644 index 000000000..f5e9d8092 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/IProfileMigration.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Nodes; + +namespace Artemis.Storage.Migrator.Legacy.Migrations; + +public interface IProfileMigration +{ + int Version { get; } + void Migrate(JsonObject configurationJson, JsonObject profileJson); +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/IStorageMigration.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/IStorageMigration.cs new file mode 100644 index 000000000..4aee9aabd --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/IStorageMigration.cs @@ -0,0 +1,9 @@ +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations; + +public interface IStorageMigration +{ + int UserVersion { get; } + void Apply(LiteRepository repository); +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0020AvaloniaReset.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0020AvaloniaReset.cs new file mode 100644 index 000000000..de3478e7b --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0020AvaloniaReset.cs @@ -0,0 +1,17 @@ +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0020AvaloniaReset : IStorageMigration +{ + public int UserVersion => 20; + + public void Apply(LiteRepository repository) + { + repository.Database.Commit(); + + List collectionNames = repository.Database.GetCollectionNames().ToList(); + foreach (string collectionName in collectionNames) + repository.Database.DropCollection(collectionName); + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0021GradientNodes.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0021GradientNodes.cs new file mode 100644 index 000000000..354525008 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0021GradientNodes.cs @@ -0,0 +1,87 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile; +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0021GradientNodes : IStorageMigration +{ + private void MigrateDataBinding(PropertyEntity property) + { + NodeScriptEntity? script = property.DataBinding?.NodeScript; + NodeEntity? exitNode = script?.Nodes.FirstOrDefault(s => s.IsExitNode); + if (script == null || exitNode == null) + return; + + // Create a new node at the same position of the exit node + NodeEntity gradientNode = new() + { + Id = Guid.NewGuid(), + Type = "ColorGradientNode", + ProviderId = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78", + Name = "Color Gradient", + Description = "Outputs a color gradient with the given colors", + X = exitNode.X, + Y = exitNode.Y, + Storage = property.Value // Copy the value of the property into the node storage + }; + script.Nodes.Add(gradientNode); + + // Move all connections of the exit node to the new node + foreach (NodeConnectionEntity connection in script.Connections) + { + if (connection.SourceNode == exitNode.Id) + { + connection.SourceNode = gradientNode.Id; + connection.SourcePinId++; + } + } + + // Connect the data binding node to the source node + script.Connections.Add(new NodeConnectionEntity + { + SourceType = "ColorGradient", + SourceNode = exitNode.Id, + SourcePinCollectionId = -1, + SourcePinId = 0, + TargetType = "ColorGradient", + TargetNode = gradientNode.Id, + TargetPinCollectionId = -1, + TargetPinId = 0 + }); + + // Move the exit node to the right + exitNode.X += 300; + exitNode.Y += 30; + } + + private void MigrateDataBinding(PropertyGroupEntity? propertyGroup) + { + if (propertyGroup == null) + return; + + foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups) + MigrateDataBinding(propertyGroupPropertyGroup); + + foreach (PropertyEntity property in propertyGroup.Properties) + { + if (property.Value.StartsWith("[{\"Color\":\"") && property.DataBinding?.NodeScript != null && property.DataBinding.IsEnabled) + MigrateDataBinding(property); + } + } + + public int UserVersion => 21; + + public void Apply(LiteRepository repository) + { + // Find all color gradient data bindings, there's no really good way to do this so infer it from the value + List profiles = repository.Query().ToList(); + foreach (ProfileEntity profileEntity in profiles) + { + foreach (LayerEntity layer in profileEntity.Layers.Where(le => le.LayerBrush != null)) + MigrateDataBinding(layer.LayerBrush?.PropertyGroup); + + repository.Update(profileEntity); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0022TransitionNodes.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0022TransitionNodes.cs new file mode 100644 index 000000000..7556b7840 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0022TransitionNodes.cs @@ -0,0 +1,84 @@ +using Artemis.Storage.Migrator.Legacy.Entities.Profile; +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; +using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0022TransitionNodes : IStorageMigration +{ + private void MigrateNodeScript(NodeScriptEntity? nodeScript) + { + if (nodeScript == null) + return; + + foreach (NodeEntity node in nodeScript.Nodes) + { + if (node.Type == "NumericEasingNode") + node.Type = "NumericTransitionNode"; + else if (node.Type == "ColorGradientEasingNode") + node.Type = "ColorGradientTransitionNode"; + else if (node.Type == "SKColorEasingNode") + node.Type = "SKColorTransitionNode"; + else if (node.Type == "EasingTypeNode") + node.Type = "EasingFunctionNode"; + } + } + + private void MigratePropertyGroup(PropertyGroupEntity? propertyGroup) + { + if (propertyGroup == null) + return; + + foreach (PropertyGroupEntity childPropertyGroup in propertyGroup.PropertyGroups) + MigratePropertyGroup(childPropertyGroup); + foreach (PropertyEntity property in propertyGroup.Properties) + MigrateNodeScript(property.DataBinding?.NodeScript); + } + + private void MigrateDisplayCondition(IConditionEntity? conditionEntity) + { + if (conditionEntity is EventConditionEntity eventConditionEntity) + MigrateNodeScript(eventConditionEntity.Script); + else if (conditionEntity is StaticConditionEntity staticConditionEntity) + MigrateNodeScript(staticConditionEntity.Script); + } + + public int UserVersion => 22; + + public void Apply(LiteRepository repository) + { + // Migrate profile configuration display conditions + List categories = repository.Query().ToList(); + foreach (ProfileCategoryEntity profileCategoryEntity in categories) + { + foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations) + MigrateNodeScript(profileConfigurationEntity.ActivationCondition); + repository.Update(profileCategoryEntity); + } + + // Migrate profile display conditions and data bindings + List profiles = repository.Query().ToList(); + foreach (ProfileEntity profileEntity in profiles) + { + foreach (LayerEntity layer in profileEntity.Layers) + { + MigratePropertyGroup(layer.LayerBrush?.PropertyGroup); + MigratePropertyGroup(layer.GeneralPropertyGroup); + MigratePropertyGroup(layer.TransformPropertyGroup); + foreach (LayerEffectEntity layerEffectEntity in layer.LayerEffects) + MigratePropertyGroup(layerEffectEntity.PropertyGroup); + MigrateDisplayCondition(layer.DisplayCondition); + } + + foreach (FolderEntity folder in profileEntity.Folders) + { + foreach (LayerEffectEntity folderLayerEffect in folder.LayerEffects) + MigratePropertyGroup(folderLayerEffect.PropertyGroup); + MigrateDisplayCondition(folder.DisplayCondition); + } + + repository.Update(profileEntity); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0023LayoutProviders.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0023LayoutProviders.cs new file mode 100644 index 000000000..602ba6f2e --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0023LayoutProviders.cs @@ -0,0 +1,33 @@ +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0023LayoutProviders : IStorageMigration +{ + public int UserVersion => 23; + + public void Apply(LiteRepository repository) + { + ILiteCollection deviceEntities = repository.Database.GetCollection("DeviceEntity"); + List toUpdate = new(); + + foreach (BsonDocument bsonDocument in deviceEntities.FindAll()) + { + if (bsonDocument.TryGetValue("CustomLayoutPath", out BsonValue customLayoutPath) && customLayoutPath.IsString && !string.IsNullOrEmpty(customLayoutPath.AsString)) + { + bsonDocument.Add("LayoutType", new BsonValue("CustomPath")); + bsonDocument.Add("LayoutParameter", new BsonValue(customLayoutPath.AsString)); + } + else if (bsonDocument.TryGetValue("DisableDefaultLayout", out BsonValue disableDefaultLayout) && disableDefaultLayout.AsBoolean) + bsonDocument.Add("LayoutType", new BsonValue("None")); + else + bsonDocument.Add("LayoutType", new BsonValue("Default")); + + bsonDocument.Remove("CustomLayoutPath"); + bsonDocument.Remove("DisableDefaultLayout"); + toUpdate.Add(bsonDocument); + } + + deviceEntities.Update(toUpdate); + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0024NodeProviders.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0024NodeProviders.cs new file mode 100644 index 000000000..37d61c5d5 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0024NodeProviders.cs @@ -0,0 +1,105 @@ +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0024NodeProviders : IStorageMigration +{ + public int UserVersion => 24; + + public void Apply(LiteRepository repository) + { + ILiteCollection categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity"); + List categoriesToUpdate = new(); + foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll()) + { + BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray; + if (profiles != null) + { + foreach (BsonValue profile in profiles) + profile["Version"] = 1; + categoriesToUpdate.Add(profileCategoryBson); + } + } + categoryCollection.Update(categoriesToUpdate); + + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + List profilesToUpdate = new(); + foreach (BsonDocument profileBson in collection.FindAll()) + { + BsonArray? folders = profileBson["Folders"]?.AsArray; + BsonArray? layers = profileBson["Layers"]?.AsArray; + + if (folders != null) + { + foreach (BsonValue folder in folders) + MigrateProfileElement(folder.AsDocument); + } + + if (layers != null) + { + foreach (BsonValue layer in layers) + { + MigrateProfileElement(layer.AsDocument); + MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument); + MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument); + MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument); + } + } + + profilesToUpdate.Add(profileBson); + } + + collection.Update(profilesToUpdate); + } + + private void MigrateProfileElement(BsonDocument profileElement) + { + BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray; + if (layerEffects != null) + { + foreach (BsonValue layerEffect in layerEffects) + MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument); + } + + BsonValue? displayCondition = profileElement["DisplayCondition"]; + if (displayCondition != null) + MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument); + } + + private void MigratePropertyGroup(BsonDocument? propertyGroup) + { + if (propertyGroup == null || propertyGroup.Keys.Count == 0) + return; + + BsonArray? properties = propertyGroup["Properties"]?.AsArray; + BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray; + + if (properties != null) + { + foreach (BsonValue property in properties) + MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument); + } + + if (propertyGroups != null) + { + foreach (BsonValue childPropertyGroup in propertyGroups) + MigratePropertyGroup(childPropertyGroup.AsDocument); + } + } + + private void MigrateNodeScript(BsonDocument? nodeScript) + { + if (nodeScript == null || nodeScript.Keys.Count == 0) + return; + + BsonArray? nodes = nodeScript["Nodes"]?.AsArray; + if (nodes == null) + return; + + foreach (BsonValue node in nodes) + { + node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes"); + node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs new file mode 100644 index 000000000..bbb121956 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs @@ -0,0 +1,45 @@ +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0025NodeProvidersProfileConfig : IStorageMigration +{ + public int UserVersion => 25; + + public void Apply(LiteRepository repository) + { + ILiteCollection categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity"); + List toUpdate = new(); + foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll()) + { + BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray; + if (profiles != null) + { + foreach (BsonValue profile in profiles) + { + profile["Version"] = 2; + MigrateNodeScript(profile["ActivationCondition"]?.AsDocument); + } + toUpdate.Add(profileCategoryBson); + } + } + + categoryCollection.Update(toUpdate); + } + + private void MigrateNodeScript(BsonDocument? nodeScript) + { + if (nodeScript == null || nodeScript.Keys.Count == 0) + return; + + BsonArray? nodes = nodeScript["Nodes"]?.AsArray; + if (nodes == null) + return; + + foreach (BsonValue node in nodes) + { + node.AsDocument["Type"] = node.AsDocument["Type"]?.AsString?.Replace("Artemis.VisualScripting.Nodes", "Artemis.Plugins.Nodes.General.Nodes"); + node.AsDocument["ProviderId"] = "Artemis.Plugins.Nodes.General.GeneralNodesProvider-d9e1ee78"; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0026NodeStorage.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0026NodeStorage.cs new file mode 100644 index 000000000..1212455bb --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0026NodeStorage.cs @@ -0,0 +1,227 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using LiteDB; +using Serilog; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0026NodeStorage : IStorageMigration +{ + private readonly ILogger _logger; + + public M0026NodeStorage(ILogger logger) + { + _logger = logger; + } + + public int UserVersion => 26; + + public void Apply(LiteRepository repository) + { + ILiteCollection categoryCollection = repository.Database.GetCollection("ProfileCategoryEntity"); + List toUpdate = new(); + foreach (BsonDocument profileCategoryBson in categoryCollection.FindAll()) + { + BsonArray? profiles = profileCategoryBson["ProfileConfigurations"]?.AsArray; + if (profiles != null) + { + foreach (BsonValue profile in profiles) + { + profile["Version"] = 4; + MigrateNodeScript(profile["ActivationCondition"]?.AsDocument); + } + + toUpdate.Add(profileCategoryBson); + } + } + + categoryCollection.Update(toUpdate); + + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + List profilesToUpdate = new(); + foreach (BsonDocument profileBson in collection.FindAll()) + { + BsonArray? folders = profileBson["Folders"]?.AsArray; + BsonArray? layers = profileBson["Layers"]?.AsArray; + + if (folders != null) + { + foreach (BsonValue folder in folders) + MigrateProfileElement(folder.AsDocument); + } + + if (layers != null) + { + foreach (BsonValue layer in layers) + { + MigrateProfileElement(layer.AsDocument); + MigratePropertyGroup(layer.AsDocument["GeneralPropertyGroup"].AsDocument); + MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument); + MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument); + } + } + + profilesToUpdate.Add(profileBson); + } + + collection.Update(profilesToUpdate); + } + + private void MigrateProfileElement(BsonDocument profileElement) + { + BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray; + if (layerEffects != null) + { + foreach (BsonValue layerEffect in layerEffects) + MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument); + } + + BsonValue? displayCondition = profileElement["DisplayCondition"]; + if (displayCondition != null) + MigrateNodeScript(displayCondition.AsDocument["Script"].AsDocument); + } + + private void MigratePropertyGroup(BsonDocument? propertyGroup) + { + if (propertyGroup == null || propertyGroup.Keys.Count == 0) + return; + + BsonArray? properties = propertyGroup["Properties"]?.AsArray; + BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray; + + if (properties != null) + { + foreach (BsonValue property in properties) + MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument); + } + + if (propertyGroups != null) + { + foreach (BsonValue childPropertyGroup in propertyGroups) + MigratePropertyGroup(childPropertyGroup.AsDocument); + } + } + + private void MigrateNodeScript(BsonDocument? nodeScript) + { + if (nodeScript == null || nodeScript.Keys.Count == 0) + return; + + BsonArray? nodes = nodeScript["Nodes"]?.AsArray; + if (nodes == null) + return; + + foreach (BsonValue node in nodes) + { + // Migrate the storage of the node + node["Storage"] = MigrateNodeStorageJson(node.AsDocument["Storage"]?.AsString, _logger); + } + } + + private static string? MigrateNodeStorageJson(string? json, ILogger logger) + { + if (string.IsNullOrEmpty(json)) + return json; + + try + { + JsonDocument jsonDocument = JsonDocument.Parse(json); + if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object) + return json; + + JsonObject? jsonObject = JsonObject.Create(jsonDocument.RootElement); + if (jsonObject == null) + return json; + + if (jsonObject["$type"] != null && jsonObject["$values"] != null) + { + JsonArray? values = jsonObject["$values"]?.AsArray(); + if (values != null) + { + foreach (JsonNode? jsonNode in values.ToList()) + { + if (jsonNode is JsonObject childObject) + ConvertToSystemTextJson(childObject); + } + + return values.ToJsonString(); + } + } + else + { + ConvertToSystemTextJson(jsonObject); + } + + return jsonObject.ToJsonString(); + } + catch (Exception e) + { + logger.Error(e, "Failed to migrate node storage JSON"); + return json; + } + } + + private static void ConvertToSystemTextJson(JsonObject jsonObject) + { + FilterType(jsonObject); + + // Recursively convert all JSON arrays from {$type: "xyz", $values: []} to [] + foreach ((string? key, JsonNode? value) in jsonObject.ToDictionary()) + { + if (value is not JsonObject obj) + continue; + + // if there is a $type and a $values, replace the entire node with $values regardless of the value of $type + if (obj["$type"] != null && obj["$values"] != null) + { + JsonArray? values = obj["$values"]?.AsArray(); + if (values != null) + { + obj.Remove("$values"); + jsonObject[key] = values; + foreach (JsonNode? jsonNode in values.ToList()) + { + if (jsonNode is JsonObject childObject) + ConvertToSystemTextJson(childObject); + } + } + + obj.Remove("$type"); + } + else + { + ConvertToSystemTextJson(obj); + } + } + } + + private static void FilterType(JsonObject jsonObject) + { + // Replace or remove $type depending on whether there's a matching JsonDerivedType + // This could be done with reflection but that would mean this migration automatically gains new behaviour over time. + JsonNode? type = jsonObject["$type"]; + if (type != null) + { + // Adaption Hints + if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.CategoryAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "Category"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.DeviceAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "Device"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.KeyboardSectionAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "KeyboardSection"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.AdaptionHints.SingleLedAdaptionHintEntity, Artemis.Storage") + jsonObject["$type"] = "SingleLed"; + // Conditions + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.AlwaysOnConditionEntity, Artemis.Storage") + jsonObject["$type"] = "AlwaysOn"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.EventConditionEntity, Artemis.Storage") + jsonObject["$type"] = "Event"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.PlayOnceConditionEntity, Artemis.Storage") + jsonObject["$type"] = "PlayOnce"; + else if (type.GetValue() == "Artemis.Storage.Entities.Profile.Conditions.StaticConditionEntity, Artemis.Storage") + jsonObject["$type"] = "Static"; + else + jsonObject.Remove("$type"); + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0027Namespace.cs b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0027Namespace.cs new file mode 100644 index 000000000..6a98a25eb --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0027Namespace.cs @@ -0,0 +1,45 @@ +using LiteDB; + +namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; + +public class M0027Namespace : IStorageMigration +{ + public int UserVersion => 27; + + public void Apply(LiteRepository repository) + { + ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); + List profilesToUpdate = new(); + + foreach (BsonDocument profileBson in collection.FindAll()) + { + MigrateDocument(profileBson); + profilesToUpdate.Add(profileBson); + } + + collection.Update(profilesToUpdate); + } + + private void MigrateDocument(BsonDocument document) + { + foreach ((string? key, BsonValue? value) in document) + { + if (key == "_type") + { + document[key] = document[key].AsString + .Replace("Artemis.Storage.Entities.Profile", "Artemis.Storage.Migrator.Legacy.Entities.Profile") + .Replace(", Artemis.Storage", ", Artemis.Storage.Migrator"); + } + else if (value.IsDocument) + MigrateDocument(value.AsDocument); + else if (value.IsArray) + { + foreach (BsonValue bsonValue in value.AsArray) + { + if (bsonValue.IsDocument) + MigrateDocument(bsonValue.AsDocument); + } + } + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs b/src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs new file mode 100644 index 000000000..86f4f1384 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs @@ -0,0 +1,34 @@ +using Artemis.Storage.Migrator.Legacy.Migrations; +using LiteDB; +using Serilog; + +namespace Artemis.Storage.Migrator.Legacy; + +public static class StorageMigrationService +{ + public static void ApplyPendingMigrations(ILogger logger, LiteRepository repository, IList migrations) + { + foreach (IStorageMigration storageMigration in migrations.OrderBy(m => m.UserVersion)) + { + if (repository.Database.UserVersion >= storageMigration.UserVersion) + continue; + + logger.Information("Applying storage migration {storageMigration} to update DB from v{oldVersion} to v{newVersion}", + storageMigration.GetType().Name, repository.Database.UserVersion, storageMigration.UserVersion); + + repository.Database.BeginTrans(); + try + { + storageMigration.Apply(repository); + } + catch (Exception) + { + repository.Database.Rollback(); + throw; + } + + repository.Database.Commit(); + repository.Database.UserVersion = storageMigration.UserVersion; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Program.cs b/src/Artemis.Storage.Migrator/Program.cs index ba637973c..ba15b7bfa 100644 --- a/src/Artemis.Storage.Migrator/Program.cs +++ b/src/Artemis.Storage.Migrator/Program.cs @@ -1,6 +1,16 @@ -using Artemis.Core.DryIoc; +using Artemis.Core; +using Artemis.Core.DryIoc; +using Artemis.Storage.Migrator.Legacy; +using Artemis.Storage.Migrator.Legacy.Entities.General; +using Artemis.Storage.Migrator.Legacy.Entities.Plugins; +using Artemis.Storage.Migrator.Legacy.Entities.Profile; +using Artemis.Storage.Migrator.Legacy.Entities.Surface; +using Artemis.Storage.Migrator.Legacy.Entities.Workshop; +using Artemis.Storage.Migrator.Legacy.Migrations.Storage; using DryIoc; +using LiteDB; using Microsoft.EntityFrameworkCore; +using Serilog; namespace Artemis.Storage.Migrator; @@ -8,12 +18,125 @@ class Program { static void Main(string[] args) { - Container container = new Container(rules => rules + using Container container = new(rules => rules .WithMicrosoftDependencyInjectionRules() .WithConcreteTypeDynamicRegistrations() .WithoutThrowOnRegisteringDisposableTransient()); container.RegisterCore(); - container.Resolve().Database.EnsureCreated(); + + ILogger logger = container.Resolve(); + ArtemisDbContext dbContext = container.Resolve(); + logger.Information("Applying pending migrations..."); + dbContext.Database.Migrate(); + logger.Information("Pending migrations applied"); + + if (!File.Exists(Path.Combine(Constants.DataFolder, "database.db"))) + { + logger.Information("No legacy database found, nothing to migrate"); + return; + } + + logger.Information("Migrating legacy database..."); + + try + { + MigrateLegacyDatabase(logger, dbContext); + // After a successful migration, keep the legacy database around for a while + File.Move(Path.Combine(Constants.DataFolder, "database.db"), Path.Combine(Constants.DataFolder, "legacy.db")); + } + catch (Exception e) + { + logger.Error(e, "Failed to migrate legacy database"); + throw; + } + finally + { + File.Delete(Path.Combine(Constants.DataFolder, "temp.db")); + } + + logger.Information("Legacy database migrated"); + } + + private static void MigrateLegacyDatabase(ILogger logger, ArtemisDbContext dbContext) + { + // Copy the database before using it, we're going to make some modifications to it and we don't want to mess up the original + string databasePath = Path.Combine(Constants.DataFolder, "database.db"); + string tempPath = Path.Combine(Constants.DataFolder, "temp.db"); + File.Copy(databasePath, tempPath, true); + + using LiteRepository repository = new($"FileName={tempPath}"); + + // Apply pending LiteDB migrations, this includes a migration that transforms namespaces to Artemis.Storage.Migrator + StorageMigrationService.ApplyPendingMigrations( + logger, + repository, + [ + new M0020AvaloniaReset(), + new M0021GradientNodes(), + new M0022TransitionNodes(), + new M0023LayoutProviders(), + new M0024NodeProviders(), + new M0025NodeProvidersProfileConfig(), + new M0026NodeStorage(logger), + new M0027Namespace(), + ] + ); + + // Devices + if (!dbContext.Devices.Any()) + { + logger.Information("Migrating devices"); + List legacyDevices = repository.Query().Include(s => s.InputIdentifiers).ToList(); + dbContext.Devices.AddRange(legacyDevices.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // Entries + if (!dbContext.Entries.Any()) + { + logger.Information("Migrating entries"); + List legacyEntries = repository.Query().ToList(); + dbContext.Entries.AddRange(legacyEntries.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // Plugins + if (!dbContext.Plugins.Any()) + { + logger.Information("Migrating plugins"); + List legacyPlugins = repository.Query().ToList(); + dbContext.Plugins.AddRange(legacyPlugins.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // PluginSettings + if (!dbContext.PluginSettings.Any()) + { + logger.Information("Migrating plugin settings"); + List legacyPluginSettings = repository.Query().ToList(); + dbContext.PluginSettings.AddRange(legacyPluginSettings.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // ProfileCategories + if (!dbContext.ProfileCategories.Any()) + { + logger.Information("Migrating profile categories"); + List legacyProfileCategories = repository.Query().ToList(); + ILiteStorage profileIcons = repository.Database.GetStorage("profileIcons"); + List legacyProfiles = repository.Query().ToList(); + dbContext.ProfileCategories.AddRange(legacyProfileCategories.Select(l => l.Migrate(logger, legacyProfiles, profileIcons))); + dbContext.SaveChanges(); + } + + // Releases + if (!dbContext.Releases.Any()) + { + logger.Information("Migrating releases"); + List legacyReleases = repository.Query().ToList(); + dbContext.Releases.AddRange(legacyReleases.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } } } \ No newline at end of file diff --git a/src/Artemis.Storage/Artemis.Storage.csproj b/src/Artemis.Storage/Artemis.Storage.csproj index 83b31c461..fa7b8fe61 100644 --- a/src/Artemis.Storage/Artemis.Storage.csproj +++ b/src/Artemis.Storage/Artemis.Storage.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs index 9157d460c..fb58e8d8a 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileConfigurationEntity.cs @@ -1,6 +1,5 @@ using System; using Artemis.Storage.Entities.Profile.Nodes; -using Serilog.Core; namespace Artemis.Storage.Entities.Profile; @@ -8,7 +7,6 @@ public class ProfileConfigurationEntity { public string Name { get; set; } = string.Empty; public string? MaterialIcon { get; set; } - public Guid FileIconId { get; set; } public int IconType { get; set; } public bool IconFill { get; set; } public int Order { get; set; } diff --git a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs index 7f93ea093..2ee9934d6 100644 --- a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs @@ -59,20 +59,27 @@ private void Update() private void LoadFromBitmap(Core.ProfileConfigurationIcon configurationIcon, Stream stream) { - _stream = stream; - if (!configurationIcon.Fill) + try { - Content = new Image {Source = new Bitmap(stream)}; - return; - } + _stream = stream; + if (!configurationIcon.Fill) + { + Content = new Image {Source = new Bitmap(stream)}; + return; + } - Content = new Border + Content = new Border + { + Background = TextElement.GetForeground(this), + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch, + OpacityMask = new ImageBrush(new Bitmap(stream)) + }; + } + catch (Exception) { - Background = TextElement.GetForeground(this), - VerticalAlignment = VerticalAlignment.Stretch, - HorizontalAlignment = HorizontalAlignment.Stretch, - OpacityMask = new ImageBrush(new Bitmap(stream)) - }; + Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; + } } private void OnDetachedFromLogicalTree(object? sender, LogicalTreeAttachmentEventArgs e) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index b88396b71..3c9d036db 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -25,7 +25,6 @@ - From 4bae9e89cf151b7008a52ef29b2ec890352f117d Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 10 Mar 2024 21:18:20 +0100 Subject: [PATCH 05/14] Storage - Clean up legacy project and refactor repositories to use short lived DB contexts --- src/Artemis.Core/Artemis.Core.csproj | 2 +- src/Artemis.Core/Constants.cs | 2 +- src/Artemis.Core/Plugins/Plugin.cs | 8 +- .../Plugins/Settings/PluginSetting.cs | 2 +- .../Plugins/Settings/PluginSettings.cs | 7 +- src/Artemis.Core/Services/DeviceService.cs | 8 +- .../Services/PluginManagementService.cs | 8 +- src/Artemis.Core/Services/SettingsService.cs | 2 +- .../Storage/Interfaces/IProfileService.cs | 8 +- .../Services/Storage/ProfileService.cs | 68 ++------ .../Artemis.Storage.Legacy.csproj} | 5 +- .../Entities/General/QueuedActionEntity.cs | 4 +- .../Entities/General/ReleaseEntity.cs | 4 +- .../General/ScriptConfigurationEntity.cs | 4 +- .../Entities/Plugins/PluginEntity.cs | 8 +- .../Entities/Plugins/PluginSettingEntity.cs | 4 +- .../Profile/Abstract/RenderElementEntity.cs | 6 +- .../CategoryAdaptionHintEntity.cs | 4 +- .../AdaptionHints/DeviceAdaptionHintEntity.cs | 4 +- .../AdaptionHints/IAdaptionHintEntity.cs | 2 +- .../KeyboardSectionAdaptionHintEntity.cs | 6 + .../SingleLedAdaptionHintEntity.cs | 4 +- .../Conditions/AlwaysOnConditionEntity.cs | 3 + .../Conditions/EventConditionEntity.cs | 6 +- .../Profile/Conditions/IConditionEntity.cs | 2 +- .../Conditions/PlayOnceConditionEntity.cs | 3 + .../Conditions/StaticConditionEntity.cs | 10 ++ .../Profile/DataBindings/DataBindingEntity.cs | 9 + .../Entities/Profile/DataModelPathEntity.cs | 4 +- .../Entities/Profile/FolderEntity.cs | 6 +- .../Entities/Profile/KeyframeEntity.cs | 4 +- .../Entities/Profile/LayerBrushEntity.cs | 4 +- .../Entities/Profile/LayerEffectEntity.cs | 4 +- .../Entities/Profile/LayerEntity.cs | 8 +- .../Entities/Profile/LedEntity.cs | 4 +- .../Profile/Nodes/NodeConnectionEntity.cs | 4 +- .../Entities/Profile/Nodes/NodeEntity.cs | 4 +- .../Profile/Nodes/NodePinCollectionEntity.cs | 4 +- .../Profile/Nodes/NodeScriptEntity.cs | 4 +- .../Entities/Profile/ProfileCategoryEntity.cs | 4 +- .../Profile/ProfileConfigurationEntity.cs | 6 +- .../ProfileConfigurationHotkeyEntity.cs | 7 + .../Entities/Profile/ProfileEntity.cs | 6 +- .../Entities/Profile/PropertyEntity.cs | 6 +- .../Entities/Profile/PropertyGroupEntity.cs | 4 +- .../Entities/Profile/TimelineEntity.cs | 4 +- .../Entities/Surface/DeviceEntity.cs | 8 +- .../Entities/Workshop/EntryEntity.cs | 4 +- .../LegacyMigrationService.cs | 163 ++++++++++++++++++ .../Migrations/IProfileMigration.cs | 2 +- .../Migrations/IStorageMigration.cs | 2 +- .../Migrations/Storage/M0020AvaloniaReset.cs | 4 +- .../Migrations/Storage/M0021GradientNodes.cs | 10 +- .../Storage/M0022TransitionNodes.cs | 10 +- .../Storage/M0023LayoutProviders.cs | 8 +- .../Migrations/Storage/M0024NodeProviders.cs | 21 +-- .../M0025NodeProvidersProfileConfig.cs | 9 +- .../Migrations/Storage/M0026NodeStorage.cs | 16 +- .../Migrations/Storage/M0027Namespace.cs | 12 +- src/Artemis.Storage.Legacy/Program.cs | 22 +++ .../KeyboardSectionAdaptionHintEntity.cs | 6 - .../Conditions/AlwaysOnConditionEntity.cs | 3 - .../Conditions/PlayOnceConditionEntity.cs | 3 - .../Conditions/StaticConditionEntity.cs | 10 -- .../Profile/DataBindings/DataBindingEntity.cs | 9 - .../ProfileConfigurationHotkeyEntity.cs | 7 - .../Legacy/StorageMigrationService.cs | 34 ---- src/Artemis.Storage.Migrator/Program.cs | 142 --------------- src/Artemis.Storage.Migrator/artemis.db | Bin 86016 -> 0 bytes src/Artemis.Storage/ArtemisDbContext.cs | 2 + .../Entities/General/ReleaseEntity.cs | 6 + .../Entities/Plugins/PluginEntity.cs | 6 +- .../Entities/Plugins/PluginSettingEntity.cs | 5 + .../Entities/Profile/ProfileCategoryEntity.cs | 4 + .../Entities/RawProfileContainer.cs | 10 ++ .../Entities/Surface/DeviceEntity.cs | 13 +- .../Entities/Workshop/EntryEntity.cs | 11 +- .../Exceptions/ArtemisStorageException.cs | 14 ++ ....cs => 20240310201706_Initial.Designer.cs} | 37 +++- ...1_Initial.cs => 20240310201706_Initial.cs} | 83 ++++++--- .../ArtemisDbContextModelSnapshot.cs | 35 +++- .../Repositories/DeviceRepository.cs | 43 +++-- .../Repositories/EntryRepository.cs | 36 ++-- .../Interfaces/IDeviceRepository.cs | 3 +- .../Interfaces/IEntryRepository.cs | 2 +- .../Interfaces/IPluginRepository.cs | 7 +- .../Interfaces/IProfileCategoryRepository.cs | 3 +- .../Interfaces/IProfileRepository.cs | 15 ++ .../Interfaces/IReleaseRepository.cs | 9 + .../Repositories/PluginRepository.cs | 45 +++-- .../Repositories/ProfileCategoryRepository.cs | 58 ++++--- .../Repositories/ProfileRepository.cs | 91 ++++++++++ .../Repositories/ReleaseRepository.cs | 26 +-- src/Artemis.UI.Linux/App.axaml.cs | 4 + src/Artemis.UI.Linux/Artemis.UI.Linux.csproj | 1 + src/Artemis.UI.MacOS/App.axaml.cs | 4 + src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj | 1 + .../Artemis.UI.Shared.csproj | 1 + src/Artemis.UI.Windows/App.axaml.cs | 4 + .../Artemis.UI.Windows.csproj | 1 + .../Services/Updating/UpdateService.cs | 1 + .../ProfileEntryInstallationHandler.cs | 2 +- .../Services/WorkshopService.cs | 7 +- src/Artemis.sln | 2 +- src/Directory.Packages.props | 3 +- 105 files changed, 815 insertions(+), 575 deletions(-) rename src/{Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj => Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj} (78%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/General/QueuedActionEntity.cs (76%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/General/ReleaseEntity.cs (82%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/General/ScriptConfigurationEntity.cs (66%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Plugins/PluginEntity.cs (88%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Plugins/PluginSettingEntity.cs (85%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/Abstract/RenderElementEntity.cs (58%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs (52%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs (53%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs (82%) create mode 100644 src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs (52%) create mode 100644 src/Artemis.Storage.Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/Conditions/EventConditionEntity.cs (55%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/Conditions/IConditionEntity.cs (81%) create mode 100644 src/Artemis.Storage.Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs create mode 100644 src/Artemis.Storage.Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs create mode 100644 src/Artemis.Storage.Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/DataModelPathEntity.cs (59%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/FolderEntity.cs (63%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/KeyframeEntity.cs (66%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/LayerBrushEntity.cs (66%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/LayerEffectEntity.cs (77%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/LayerEntity.cs (73%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/LedEntity.cs (92%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/Nodes/NodeConnectionEntity.cs (90%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/Nodes/NodeEntity.cs (92%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/Nodes/NodePinCollectionEntity.cs (79%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/Nodes/NodeScriptEntity.cs (78%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/ProfileCategoryEntity.cs (96%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/ProfileConfigurationEntity.cs (83%) create mode 100644 src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/ProfileEntity.cs (84%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/PropertyEntity.cs (64%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/PropertyGroupEntity.cs (68%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Profile/TimelineEntity.cs (63%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Surface/DeviceEntity.cs (93%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Entities/Workshop/EntryEntity.cs (91%) create mode 100644 src/Artemis.Storage.Legacy/LegacyMigrationService.cs rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/IProfileMigration.cs (75%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/IStorageMigration.cs (70%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0020AvaloniaReset.cs (76%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0021GradientNodes.cs (92%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0022TransitionNodes.cs (91%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0023LayoutProviders.cs (88%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0024NodeProviders.cs (93%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0025NodeProvidersProfileConfig.cs (91%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0026NodeStorage.cs (97%) rename src/{Artemis.Storage.Migrator/Legacy => Artemis.Storage.Legacy}/Migrations/Storage/M0027Namespace.cs (79%) create mode 100644 src/Artemis.Storage.Legacy/Program.cs delete mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs delete mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs delete mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs delete mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs delete mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs delete mode 100644 src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs delete mode 100644 src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs delete mode 100644 src/Artemis.Storage.Migrator/Program.cs delete mode 100644 src/Artemis.Storage.Migrator/artemis.db create mode 100644 src/Artemis.Storage/Entities/RawProfileContainer.cs create mode 100644 src/Artemis.Storage/Exceptions/ArtemisStorageException.cs rename src/Artemis.Storage/Migrations/{20240308203921_Initial.Designer.cs => 20240310201706_Initial.Designer.cs} (90%) rename src/Artemis.Storage/Migrations/{20240308203921_Initial.cs => 20240310201706_Initial.cs} (74%) create mode 100644 src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs create mode 100644 src/Artemis.Storage/Repositories/Interfaces/IReleaseRepository.cs create mode 100644 src/Artemis.Storage/Repositories/ProfileRepository.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index 75fa9a3a1..42daefc59 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -40,7 +40,7 @@ - + diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 79d19cc48..dfa7791c5 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -91,7 +91,7 @@ public static class Constants /// /// The plugin used by core components of Artemis /// - public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), new PluginEntity(){Id = CorePluginInfo.Guid}, false); + public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), new PluginEntity(){PluginGuid = CorePluginInfo.Guid}, false); /// /// A read-only collection containing all primitive numeric types diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 2b5ce51d1..5eacf18f0 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -309,13 +309,7 @@ protected virtual void OnFeatureRemoved(PluginFeatureInfoEventArgs e) { FeatureRemoved?.Invoke(this, e); } - - internal void ApplyToEntity() - { - Entity.Id = Guid; - Entity.IsEnabled = IsEnabled; - } - + internal void AddFeature(PluginFeatureInfo featureInfo) { if (featureInfo.Plugin != this) diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs index e3510a58e..8f14d542f 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs @@ -94,7 +94,7 @@ public void Save() return; _pluginSettingEntity.Value = CoreJson.Serialize(Value); - _pluginRepository.SaveChanges(); + _pluginRepository.SaveSetting(_pluginSettingEntity); OnSettingSaved(); } diff --git a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs index 89c1a10ea..811dcf817 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSettings.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSettings.cs @@ -31,10 +31,13 @@ internal PluginSettings(Plugin plugin, IPluginRepository pluginRepository) /// Gets the setting with the provided name. If the setting does not exist yet, it is created. /// /// The type of the setting, can be any serializable type - /// The name of the setting + /// The name of the setting, may not be longer than 128 characters /// The default value to use if the setting does not exist yet public PluginSetting GetSetting(string name, T? defaultValue = default) { + if (name.Length > 128) + throw new ArtemisCoreException("Setting name cannot be longer than 128 characters"); + lock (_settingEntities) { // Return cached value if available @@ -51,7 +54,7 @@ public PluginSetting GetSetting(string name, T? defaultValue = default) PluginGuid = Plugin.Guid, Value = CoreJson.Serialize(defaultValue) }; - _pluginRepository.AddSetting(settingEntity); + _pluginRepository.SaveSetting(settingEntity); } PluginSetting pluginSetting = new(_pluginRepository, settingEntity); diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index 46399722e..da81a82a6 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -202,7 +202,7 @@ public void EnableDevice(ArtemisDevice device) _enabledDevices.Add(device); device.IsEnabled = true; device.Save(); - _deviceRepository.SaveChanges(); + _deviceRepository.Save(device.DeviceEntity); OnDeviceEnabled(new DeviceEventArgs(device)); UpdateLeds(); @@ -217,7 +217,7 @@ public void DisableDevice(ArtemisDevice device) _enabledDevices.Remove(device); device.IsEnabled = false; device.Save(); - _deviceRepository.SaveChanges(); + _deviceRepository.Save(device.DeviceEntity); OnDeviceDisabled(new DeviceEventArgs(device)); UpdateLeds(); @@ -227,7 +227,7 @@ public void DisableDevice(ArtemisDevice device) public void SaveDevice(ArtemisDevice artemisDevice) { artemisDevice.Save(); - _deviceRepository.SaveChanges(); + _deviceRepository.Save(artemisDevice.DeviceEntity); UpdateLeds(); } @@ -236,7 +236,7 @@ public void SaveDevices() { foreach (ArtemisDevice artemisDevice in _devices) artemisDevice.Save(); - _deviceRepository.SaveChanges(); + _deviceRepository.SaveRange(_devices.Select(d => d.DeviceEntity)); UpdateLeds(); } diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 5b31b31d9..62bc658ac 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -372,12 +372,12 @@ public void UnloadPlugins() } // Load the entity and fall back on creating a new one - PluginEntity? entity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid); + PluginEntity? entity = _pluginRepository.GetPluginByPluginGuid(pluginInfo.Guid); bool loadedFromStorage = entity != null; if (entity == null) { - entity = new PluginEntity {Id = pluginInfo.Guid}; - _pluginRepository.AddPlugin(entity); + entity = new PluginEntity {PluginGuid = pluginInfo.Guid}; + _pluginRepository.SavePlugin(entity); } Plugin plugin = new(pluginInfo, directory, entity, loadedFromStorage); @@ -815,7 +815,7 @@ private void SavePlugin(Plugin plugin) plugin.Entity.Features.Add(featureInfo.Instance!.Entity); } - _pluginRepository.SaveChanges(); + _pluginRepository.SavePlugin(plugin.Entity); } #endregion diff --git a/src/Artemis.Core/Services/SettingsService.cs b/src/Artemis.Core/Services/SettingsService.cs index 96719eae0..a3cf4c030 100644 --- a/src/Artemis.Core/Services/SettingsService.cs +++ b/src/Artemis.Core/Services/SettingsService.cs @@ -34,7 +34,7 @@ public interface ISettingsService : IProtectedArtemisService /// Gets the setting with the provided name. If the setting does not exist yet, it is created. /// /// The type of the setting, can be any serializable type - /// The name of the setting + /// The name of the setting, may not be longer than 128 characters /// The default value to use if the setting does not exist yet /// PluginSetting GetSetting(string name, T? defaultValue = default); diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index 47766f5bb..c8a5f8435 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -54,13 +54,7 @@ public interface IProfileService : IArtemisService /// /// The profile configuration of the profile to activate. void DeactivateProfile(ProfileConfiguration profileConfiguration); - - /// - /// Permanently deletes the profile of the given . - /// - /// The profile configuration of the profile to delete. - void DeleteProfile(ProfileConfiguration profileConfiguration); - + /// /// Saves the provided and it's s but not the /// s themselves. diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 423f50797..6b9889e19 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -22,6 +22,7 @@ internal class ProfileService : IProfileService { private readonly ILogger _logger; private readonly IProfileCategoryRepository _profileCategoryRepository; + private readonly IProfileRepository _profileRepository; private readonly IPluginManagementService _pluginManagementService; private readonly IDeviceService _deviceService; private readonly List _pendingKeyboardEvents = new(); @@ -34,6 +35,7 @@ internal class ProfileService : IProfileService public ProfileService(ILogger logger, IProfileCategoryRepository profileCategoryRepository, + IProfileRepository profileRepository, IPluginManagementService pluginManagementService, IInputService inputService, IDeviceService deviceService, @@ -41,6 +43,7 @@ public ProfileService(ILogger logger, { _logger = logger; _profileCategoryRepository = profileCategoryRepository; + _profileRepository = profileRepository; _pluginManagementService = pluginManagementService; _deviceService = deviceService; _profileMigrators = profileMigrators; @@ -214,20 +217,7 @@ private void RequestDeactivation(ProfileConfiguration profileConfiguration) profileConfiguration.Profile.ShouldDisplay = false; } - - /// - public void DeleteProfile(ProfileConfiguration profileConfiguration) - { - DeactivateProfile(profileConfiguration); - - ProfileCategory category = profileConfiguration.Category; - - category.RemoveProfileConfiguration(profileConfiguration); - category.Entity.ProfileConfigurations.Remove(profileConfiguration.Entity); - - _profileCategoryRepository.SaveChanges(); - } - + /// public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) { @@ -240,6 +230,8 @@ public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) category.Order++; category.Save(); } + + _profileCategoryRepository.SaveRange(ProfileCategories.Select(c => c.Entity).ToList()); } else { @@ -274,32 +266,32 @@ public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, SaveProfileCategory(category); return configuration; } - + /// public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration) { + DeactivateProfile(profileConfiguration); + ProfileCategory category = profileConfiguration.Category; - category.RemoveProfileConfiguration(profileConfiguration); - DeactivateProfile(profileConfiguration); - SaveProfileCategory(profileConfiguration.Category); + category.RemoveProfileConfiguration(profileConfiguration); + category.Save(); - profileConfiguration.Dispose(); + _profileRepository.Remove(profileConfiguration.Entity); + _profileCategoryRepository.Save(category.Entity); } /// public void SaveProfileCategory(ProfileCategory profileCategory) { profileCategory.Save(); - _profileCategoryRepository.SaveChanges(); + _profileCategoryRepository.Save(profileCategory.Entity); ProfileCategories = new ReadOnlyCollection(ProfileCategories.OrderBy(c => c.Order).ToList()); } /// public void SaveProfile(Profile profile, bool includeChildren) { - Stopwatch sw = new(); - sw.Start(); _logger.Debug("Updating profile - Saving {Profile}", profile); profile.Save(); if (includeChildren) @@ -312,7 +304,7 @@ public void SaveProfile(Profile profile, bool includeChildren) profile.IsFreshImport = false; profile.ProfileEntity.IsFreshImport = false; - SaveProfileCategory(profile.Configuration.Category); + _profileRepository.Save(profile.Configuration.Entity); // If the provided profile is external (cloned or from the workshop?) but it is loaded locally too, reload the local instance // A bit dodge but it ensures local instances always represent the latest stored version @@ -320,17 +312,10 @@ public void SaveProfile(Profile profile, bool includeChildren) .SelectMany(c => c.ProfileConfigurations) .FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id); if (localInstance == null) - { - sw.Stop(); - _logger.Debug("Updated profile - Saved {Profile} in {Time}ms", profile, sw.Elapsed.TotalMilliseconds); return; - } DeactivateProfile(localInstance); ActivateProfile(localInstance); - - sw.Stop(); - _logger.Debug("Updated profile - Saved {Profile} in {Time}ms", profile, sw.Elapsed.TotalMilliseconds); } /// @@ -393,7 +378,7 @@ public async Task ImportProfile(Stream archiveStream, Prof JsonObject? profileJson = CoreJson.Deserialize(await profileReader.ReadToEndAsync()); // Before deserializing, apply any pending migrations - MigrateProfile(configurationJson, profileJson); + _profileRepository.MigrateProfile(configurationJson, profileJson); // Deserialize profile configuration to ProfileConfigurationEntity ProfileConfigurationEntity? configurationEntity = configurationJson?.Deserialize(Constants.JsonConvertSettings); @@ -453,7 +438,7 @@ public async Task OverwriteProfile(MemoryStream archiveStr { ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration.Order + 1); - DeleteProfile(profileConfiguration); + RemoveProfileConfiguration(profileConfiguration); SaveProfileCategory(imported.Category); return imported; @@ -479,24 +464,7 @@ private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEvent { _pendingKeyboardEvents.Add(e); } - - private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson) - { - if (configurationJson == null || profileJson == null) - return; - - configurationJson["Version"] ??= 0; - - foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version)) - { - if (profileMigrator.Version <= configurationJson["Version"]!.GetValue()) - continue; - - profileMigrator.Migrate(configurationJson, profileJson); - configurationJson["Version"] = profileMigrator.Version; - } - } - + /// /// Populates all missing LEDs on all currently active profiles /// diff --git a/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj b/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj similarity index 78% rename from src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj rename to src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj index dbc3a660d..2da8bd8c3 100644 --- a/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj +++ b/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj @@ -1,7 +1,6 @@  - Exe net8.0 enable enable @@ -13,8 +12,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/General/QueuedActionEntity.cs b/src/Artemis.Storage.Legacy/Entities/General/QueuedActionEntity.cs similarity index 76% rename from src/Artemis.Storage.Migrator/Legacy/Entities/General/QueuedActionEntity.cs rename to src/Artemis.Storage.Legacy/Entities/General/QueuedActionEntity.cs index 8244c57ec..90770d565 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/General/QueuedActionEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/General/QueuedActionEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.General; +namespace Artemis.Storage.Legacy.Entities.General; -public class QueuedActionEntity +internal class QueuedActionEntity { public QueuedActionEntity() { diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/General/ReleaseEntity.cs b/src/Artemis.Storage.Legacy/Entities/General/ReleaseEntity.cs similarity index 82% rename from src/Artemis.Storage.Migrator/Legacy/Entities/General/ReleaseEntity.cs rename to src/Artemis.Storage.Legacy/Entities/General/ReleaseEntity.cs index a0d9795bf..a8aea26a8 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/General/ReleaseEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/General/ReleaseEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.General; +namespace Artemis.Storage.Legacy.Entities.General; -public class ReleaseEntity +internal class ReleaseEntity { public Guid Id { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/General/ScriptConfigurationEntity.cs b/src/Artemis.Storage.Legacy/Entities/General/ScriptConfigurationEntity.cs similarity index 66% rename from src/Artemis.Storage.Migrator/Legacy/Entities/General/ScriptConfigurationEntity.cs rename to src/Artemis.Storage.Legacy/Entities/General/ScriptConfigurationEntity.cs index 4c1743fb6..94fce07e4 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/General/ScriptConfigurationEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/General/ScriptConfigurationEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.General; +namespace Artemis.Storage.Legacy.Entities.General; -public class ScriptConfigurationEntity +internal class ScriptConfigurationEntity { public Guid Id { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginEntity.cs similarity index 88% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Plugins/PluginEntity.cs index 779b42fff..47e2ba74b 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginEntity.cs @@ -1,9 +1,9 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Plugins; +namespace Artemis.Storage.Legacy.Entities.Plugins; /// /// Represents the configuration of a plugin, each plugin has one configuration /// -public class PluginEntity +internal class PluginEntity { public PluginEntity() { @@ -19,7 +19,7 @@ public Artemis.Storage.Entities.Plugins.PluginEntity Migrate() { return new Artemis.Storage.Entities.Plugins.PluginEntity() { - Id = Id, + PluginGuid = Id, IsEnabled = IsEnabled, Features = Features.Select(f => f.Migrate()).ToList() }; @@ -29,7 +29,7 @@ public Artemis.Storage.Entities.Plugins.PluginEntity Migrate() /// /// Represents the configuration of a plugin feature, each feature has one configuration /// -public class PluginFeatureEntity +internal class PluginFeatureEntity { public string Type { get; set; } = string.Empty; public bool IsEnabled { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginSettingEntity.cs b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginSettingEntity.cs similarity index 85% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginSettingEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Plugins/PluginSettingEntity.cs index 2d10b80e2..587e7d1c8 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Plugins/PluginSettingEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginSettingEntity.cs @@ -1,9 +1,9 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Plugins; +namespace Artemis.Storage.Legacy.Entities.Plugins; /// /// Represents the setting of a plugin, a plugin can have multiple settings /// -public class PluginSettingEntity +internal class PluginSettingEntity { public Guid Id { get; set; } public Guid PluginGuid { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Abstract/RenderElementEntity.cs similarity index 58% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Abstract/RenderElementEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/Abstract/RenderElementEntity.cs index 98f01a41e..56b4e97fd 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Abstract/RenderElementEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Abstract/RenderElementEntity.cs @@ -1,8 +1,8 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; +using Artemis.Storage.Legacy.Entities.Profile.Conditions; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract; +namespace Artemis.Storage.Legacy.Entities.Profile.Abstract; -public abstract class RenderElementEntity +internal abstract class RenderElementEntity { public Guid Id { get; set; } public Guid ParentId { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs similarity index 52% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs index 3576c5cde..16ceb4d79 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; +namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints; -public class CategoryAdaptionHintEntity : IAdaptionHintEntity +internal class CategoryAdaptionHintEntity : IAdaptionHintEntity { public int Category { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs similarity index 53% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs index f7a078867..dc4005707 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; +namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints; -public class DeviceAdaptionHintEntity : IAdaptionHintEntity +internal class DeviceAdaptionHintEntity : IAdaptionHintEntity { public int DeviceType { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs similarity index 82% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs index 9ff03f4bd..a18f3b6ac 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; +namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints; [JsonDerivedType(typeof(CategoryAdaptionHintEntity), "Category")] [JsonDerivedType(typeof(DeviceAdaptionHintEntity), "Device")] diff --git a/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs new file mode 100644 index 000000000..4d67359c6 --- /dev/null +++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs @@ -0,0 +1,6 @@ +namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints; + +internal class KeyboardSectionAdaptionHintEntity : IAdaptionHintEntity +{ + public int Section { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs similarity index 52% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs index 98d192595..9d03535d3 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; +namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints; -public class SingleLedAdaptionHintEntity : IAdaptionHintEntity +internal class SingleLedAdaptionHintEntity : IAdaptionHintEntity { public int LedId { get; set; } diff --git a/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs new file mode 100644 index 000000000..498d97885 --- /dev/null +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs @@ -0,0 +1,3 @@ +namespace Artemis.Storage.Legacy.Entities.Profile.Conditions; + +internal class AlwaysOnConditionEntity : IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/EventConditionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/EventConditionEntity.cs similarity index 55% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/EventConditionEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/Conditions/EventConditionEntity.cs index 2fea241b0..574d5d42e 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/EventConditionEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/EventConditionEntity.cs @@ -1,8 +1,8 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +using Artemis.Storage.Legacy.Entities.Profile.Nodes; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; +namespace Artemis.Storage.Legacy.Entities.Profile.Conditions; -public class EventConditionEntity : IConditionEntity +internal class EventConditionEntity : IConditionEntity { public int TriggerMode { get; set; } public int OverlapMode { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/IConditionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/IConditionEntity.cs similarity index 81% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/IConditionEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/Conditions/IConditionEntity.cs index bc78042aa..ab5086aa9 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/IConditionEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/IConditionEntity.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; +namespace Artemis.Storage.Legacy.Entities.Profile.Conditions; [JsonDerivedType(typeof(AlwaysOnConditionEntity), "AlwaysOn")] [JsonDerivedType(typeof(EventConditionEntity), "Event")] diff --git a/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs new file mode 100644 index 000000000..798ef08c2 --- /dev/null +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs @@ -0,0 +1,3 @@ +namespace Artemis.Storage.Legacy.Entities.Profile.Conditions; + +internal class PlayOnceConditionEntity : IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs new file mode 100644 index 000000000..a4bfcc06b --- /dev/null +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs @@ -0,0 +1,10 @@ +using Artemis.Storage.Legacy.Entities.Profile.Nodes; + +namespace Artemis.Storage.Legacy.Entities.Profile.Conditions; + +internal class StaticConditionEntity : IConditionEntity +{ + public int PlayMode { get; set; } + public int StopMode { get; set; } + public NodeScriptEntity? Script { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs new file mode 100644 index 000000000..39be84d72 --- /dev/null +++ b/src/Artemis.Storage.Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs @@ -0,0 +1,9 @@ +using Artemis.Storage.Legacy.Entities.Profile.Nodes; + +namespace Artemis.Storage.Legacy.Entities.Profile.DataBindings; + +internal class DataBindingEntity +{ + public bool IsEnabled { get; set; } + public NodeScriptEntity? NodeScript { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataModelPathEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/DataModelPathEntity.cs similarity index 59% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataModelPathEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/DataModelPathEntity.cs index 0783e603f..4ebef8bc4 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataModelPathEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/DataModelPathEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class DataModelPathEntity +internal class DataModelPathEntity { public string Path { get; set; } = string.Empty; public string? DataModelId { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/FolderEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/FolderEntity.cs similarity index 63% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/FolderEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/FolderEntity.cs index ef181ff5d..ae467caa7 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/FolderEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/FolderEntity.cs @@ -1,9 +1,9 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract; +using Artemis.Storage.Legacy.Entities.Profile.Abstract; using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class FolderEntity : RenderElementEntity +internal class FolderEntity : RenderElementEntity { public int Order { get; set; } public string? Name { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/KeyframeEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/KeyframeEntity.cs similarity index 66% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/KeyframeEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/KeyframeEntity.cs index fae285769..6f75fe047 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/KeyframeEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/KeyframeEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class KeyframeEntity +internal class KeyframeEntity { public TimeSpan Position { get; set; } public int Timeline { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerBrushEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LayerBrushEntity.cs similarity index 66% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerBrushEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/LayerBrushEntity.cs index f0579e9ac..254c19e71 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerBrushEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/LayerBrushEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class LayerBrushEntity +internal class LayerBrushEntity { public string ProviderId { get; set; } = string.Empty; public string BrushType { get; set; } = string.Empty; diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEffectEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEffectEntity.cs similarity index 77% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEffectEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/LayerEffectEntity.cs index b3b8e5bdb..7f4c7e696 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEffectEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEffectEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class LayerEffectEntity +internal class LayerEffectEntity { public string ProviderId { get; set; } = string.Empty; public string EffectType { get; set; } = string.Empty; diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEntity.cs similarity index 73% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/LayerEntity.cs index 57d5ea555..d00a62c20 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LayerEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEntity.cs @@ -1,10 +1,10 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Abstract; -using Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; +using Artemis.Storage.Legacy.Entities.Profile.Abstract; +using Artemis.Storage.Legacy.Entities.Profile.AdaptionHints; using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class LayerEntity : RenderElementEntity +internal class LayerEntity : RenderElementEntity { public LayerEntity() { diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LedEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LedEntity.cs similarity index 92% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LedEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/LedEntity.cs index b456a138c..5f7328bd6 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/LedEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/LedEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class LedEntity +internal class LedEntity { public string LedName { get; set; } = string.Empty; public string DeviceIdentifier { get; set; } = string.Empty; diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs similarity index 90% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs index d256cce6b..c186db470 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +namespace Artemis.Storage.Legacy.Entities.Profile.Nodes; -public class NodeConnectionEntity +internal class NodeConnectionEntity { public NodeConnectionEntity() { diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeEntity.cs similarity index 92% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeEntity.cs index b25e9b0f7..314964430 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +namespace Artemis.Storage.Legacy.Entities.Profile.Nodes; -public class NodeEntity +internal class NodeEntity { public NodeEntity() { diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs similarity index 79% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs index cdec98e6a..b10944b48 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +namespace Artemis.Storage.Legacy.Entities.Profile.Nodes; -public class NodePinCollectionEntity +internal class NodePinCollectionEntity { public NodePinCollectionEntity() { diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs similarity index 78% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs index 5afeb39df..8da434571 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +namespace Artemis.Storage.Legacy.Entities.Profile.Nodes; -public class NodeScriptEntity +internal class NodeScriptEntity { public NodeScriptEntity() { diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileCategoryEntity.cs similarity index 96% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileCategoryEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/ProfileCategoryEntity.cs index 0323db70d..c5b61012b 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileCategoryEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileCategoryEntity.cs @@ -3,9 +3,9 @@ using LiteDB; using Serilog; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class ProfileCategoryEntity +internal class ProfileCategoryEntity { public Guid Id { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationEntity.cs similarity index 83% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationEntity.cs index 89f68fe1d..482672a60 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationEntity.cs @@ -1,8 +1,8 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +using Artemis.Storage.Legacy.Entities.Profile.Nodes; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class ProfileConfigurationEntity +internal class ProfileConfigurationEntity { public string Name { get; set; } = string.Empty; public string? MaterialIcon { get; set; } diff --git a/src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs new file mode 100644 index 000000000..30cfb4a06 --- /dev/null +++ b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs @@ -0,0 +1,7 @@ +namespace Artemis.Storage.Legacy.Entities.Profile; + +internal class ProfileConfigurationHotkeyEntity +{ + public int? Key { get; set; } + public int? Modifiers { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileEntity.cs similarity index 84% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/ProfileEntity.cs index cbfde4b61..dbbbfee9d 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileEntity.cs @@ -1,8 +1,8 @@ -using Artemis.Storage.Migrator.Legacy.Entities.General; +using Artemis.Storage.Legacy.Entities.General; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class ProfileEntity +internal class ProfileEntity { public ProfileEntity() { diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyEntity.cs similarity index 64% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/PropertyEntity.cs index 1a0a5d4b6..44a4406ab 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyEntity.cs @@ -1,8 +1,8 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.DataBindings; +using Artemis.Storage.Legacy.Entities.Profile.DataBindings; -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class PropertyEntity +internal class PropertyEntity { public string Identifier { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyGroupEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyGroupEntity.cs similarity index 68% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyGroupEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/PropertyGroupEntity.cs index 8097d36ba..2c7f316bb 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/PropertyGroupEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyGroupEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class PropertyGroupEntity +internal class PropertyGroupEntity { public string Identifier { get; set; } = string.Empty; public List Properties { get; set; } = new(); diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/TimelineEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/TimelineEntity.cs similarity index 63% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Profile/TimelineEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Profile/TimelineEntity.cs index a9401abac..9a1f400e4 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/TimelineEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Profile/TimelineEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; +namespace Artemis.Storage.Legacy.Entities.Profile; -public class TimelineEntity +internal class TimelineEntity { public TimeSpan StartSegmentLength { get; set; } public TimeSpan MainSegmentLength { get; set; } diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage.Legacy/Entities/Surface/DeviceEntity.cs similarity index 93% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Surface/DeviceEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Surface/DeviceEntity.cs index 52d86fbcc..fc3988dba 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Surface/DeviceEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Surface/DeviceEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Surface; +namespace Artemis.Storage.Legacy.Entities.Surface; -public class DeviceEntity +internal class DeviceEntity { public DeviceEntity() { @@ -65,13 +65,13 @@ public Storage.Entities.Surface.DeviceEntity Migrate() } } -public class InputMappingEntity +internal class InputMappingEntity { public int OriginalLedId { get; set; } public int MappedLedId { get; set; } } -public class DeviceInputIdentifierEntity +internal class DeviceInputIdentifierEntity { public string InputProvider { get; set; } = string.Empty; public object Identifier { get; set; } = string.Empty; diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs similarity index 91% rename from src/Artemis.Storage.Migrator/Legacy/Entities/Workshop/EntryEntity.cs rename to src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs index c5fea30d2..ec0d6c091 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs @@ -1,6 +1,6 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Workshop; +namespace Artemis.Storage.Legacy.Entities.Workshop; -public class EntryEntity +internal class EntryEntity { public Guid Id { get; set; } diff --git a/src/Artemis.Storage.Legacy/LegacyMigrationService.cs b/src/Artemis.Storage.Legacy/LegacyMigrationService.cs new file mode 100644 index 000000000..1c3b35d4f --- /dev/null +++ b/src/Artemis.Storage.Legacy/LegacyMigrationService.cs @@ -0,0 +1,163 @@ +using Artemis.Core; +using Artemis.Storage.Legacy.Entities.General; +using Artemis.Storage.Legacy.Entities.Plugins; +using Artemis.Storage.Legacy.Entities.Profile; +using Artemis.Storage.Legacy.Entities.Surface; +using Artemis.Storage.Legacy.Entities.Workshop; +using Artemis.Storage.Legacy.Migrations; +using Artemis.Storage.Legacy.Migrations.Storage; +using DryIoc; +using LiteDB; +using Serilog; + +namespace Artemis.Storage.Legacy; + +public static class LegacyMigrationService +{ + public static void MigrateToSqlite(IContainer container) + { + ILogger logger = container.Resolve(); + + // Before creating a DB context which is kinda expensive, check if there's anything to migrate + if (!File.Exists(Path.Combine(Constants.DataFolder, "database.db"))) + { + logger.Information("No legacy database found, nothing to migrate"); + return; + } + + using ArtemisDbContext dbContext = container.Resolve(); + MigrateToSqlite(logger, dbContext); + } + + public static void MigrateToSqlite(ILogger logger, ArtemisDbContext dbContext) + { + if (!File.Exists(Path.Combine(Constants.DataFolder, "database.db"))) + { + logger.Information("No legacy database found, nothing to migrate"); + return; + } + + logger.Information("Migrating legacy database..."); + + try + { + // Copy the database before using it, we're going to make some modifications to it and we don't want to mess up the original + string databasePath = Path.Combine(Constants.DataFolder, "database.db"); + string tempPath = Path.Combine(Constants.DataFolder, "temp.db"); + File.Copy(databasePath, tempPath, true); + + using LiteRepository repository = new($"FileName={tempPath}"); + + // Apply pending LiteDB migrations, this includes a migration that transforms namespaces to Artemis.Storage.Legacy + ApplyPendingMigrations(logger, repository); + + // Devices + if (!dbContext.Devices.Any()) + { + logger.Information("Migrating devices"); + List legacyDevices = repository.Query().Include(s => s.InputIdentifiers).ToList(); + dbContext.Devices.AddRange(legacyDevices.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // Entries + if (!dbContext.Entries.Any()) + { + logger.Information("Migrating entries"); + List legacyEntries = repository.Query().ToList(); + dbContext.Entries.AddRange(legacyEntries.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // Plugins + if (!dbContext.Plugins.Any()) + { + logger.Information("Migrating plugins"); + List legacyPlugins = repository.Query().ToList(); + dbContext.Plugins.AddRange(legacyPlugins.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // PluginSettings + if (!dbContext.PluginSettings.Any()) + { + logger.Information("Migrating plugin settings"); + List legacyPluginSettings = repository.Query().ToList(); + dbContext.PluginSettings.AddRange(legacyPluginSettings.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // ProfileCategories + if (!dbContext.ProfileCategories.Any()) + { + logger.Information("Migrating profile categories"); + List legacyProfileCategories = repository.Query().ToList(); + ILiteStorage profileIcons = repository.Database.GetStorage("profileIcons"); + List legacyProfiles = repository.Query().ToList(); + dbContext.ProfileCategories.AddRange(legacyProfileCategories.Select(l => l.Migrate(logger, legacyProfiles, profileIcons))); + dbContext.SaveChanges(); + } + + // Releases + if (!dbContext.Releases.Any()) + { + logger.Information("Migrating releases"); + List legacyReleases = repository.Query().ToList(); + dbContext.Releases.AddRange(legacyReleases.Select(l => l.Migrate())); + dbContext.SaveChanges(); + } + + // After a successful migration, keep the legacy database around for a while + File.Move(Path.Combine(Constants.DataFolder, "database.db"), Path.Combine(Constants.DataFolder, "legacy.db")); + + logger.Information("Legacy database migrated"); + } + catch (Exception e) + { + logger.Error(e, "Failed to migrate legacy database"); + throw; + } + finally + { + File.Delete(Path.Combine(Constants.DataFolder, "temp.db")); + } + } + + private static void ApplyPendingMigrations(ILogger logger, LiteRepository repository) + { + List migrations = + [ + new M0020AvaloniaReset(), + new M0021GradientNodes(), + new M0022TransitionNodes(), + new M0023LayoutProviders(), + new M0024NodeProviders(), + new M0025NodeProvidersProfileConfig(), + new M0026NodeStorage(logger), + new M0027Namespace() + ]; + + foreach (IStorageMigration storageMigration in migrations.OrderBy(m => m.UserVersion)) + { + if (repository.Database.UserVersion >= storageMigration.UserVersion) + continue; + + logger.Information("Applying storage migration {storageMigration} to update DB from v{oldVersion} to v{newVersion}", + storageMigration.GetType().Name, repository.Database.UserVersion, storageMigration.UserVersion); + + repository.Database.BeginTrans(); + try + { + storageMigration.Apply(repository); + } + catch (Exception) + { + repository.Database.Rollback(); + throw; + } + + repository.Database.Commit(); + repository.Database.UserVersion = storageMigration.UserVersion; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/IProfileMigration.cs b/src/Artemis.Storage.Legacy/Migrations/IProfileMigration.cs similarity index 75% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/IProfileMigration.cs rename to src/Artemis.Storage.Legacy/Migrations/IProfileMigration.cs index f5e9d8092..53d1d1eda 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/IProfileMigration.cs +++ b/src/Artemis.Storage.Legacy/Migrations/IProfileMigration.cs @@ -1,6 +1,6 @@ using System.Text.Json.Nodes; -namespace Artemis.Storage.Migrator.Legacy.Migrations; +namespace Artemis.Storage.Legacy.Migrations; public interface IProfileMigration { diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/IStorageMigration.cs b/src/Artemis.Storage.Legacy/Migrations/IStorageMigration.cs similarity index 70% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/IStorageMigration.cs rename to src/Artemis.Storage.Legacy/Migrations/IStorageMigration.cs index 4aee9aabd..18ba559ca 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/IStorageMigration.cs +++ b/src/Artemis.Storage.Legacy/Migrations/IStorageMigration.cs @@ -1,6 +1,6 @@ using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations; +namespace Artemis.Storage.Legacy.Migrations; public interface IStorageMigration { diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0020AvaloniaReset.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0020AvaloniaReset.cs similarity index 76% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0020AvaloniaReset.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0020AvaloniaReset.cs index de3478e7b..685585ffb 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0020AvaloniaReset.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0020AvaloniaReset.cs @@ -1,8 +1,8 @@ using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0020AvaloniaReset : IStorageMigration +internal class M0020AvaloniaReset : IStorageMigration { public int UserVersion => 20; diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0021GradientNodes.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0021GradientNodes.cs similarity index 92% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0021GradientNodes.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0021GradientNodes.cs index 354525008..02998e98c 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0021GradientNodes.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0021GradientNodes.cs @@ -1,10 +1,10 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile; -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +using Artemis.Storage.Legacy.Entities.Profile; +using Artemis.Storage.Legacy.Entities.Profile.Nodes; using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0021GradientNodes : IStorageMigration +internal class M0021GradientNodes : IStorageMigration { private void MigrateDataBinding(PropertyEntity property) { @@ -59,7 +59,7 @@ private void MigrateDataBinding(PropertyGroupEntity? propertyGroup) { if (propertyGroup == null) return; - + foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups) MigrateDataBinding(propertyGroupPropertyGroup); diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0022TransitionNodes.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0022TransitionNodes.cs similarity index 91% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0022TransitionNodes.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0022TransitionNodes.cs index 7556b7840..369ab130c 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0022TransitionNodes.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0022TransitionNodes.cs @@ -1,11 +1,11 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile; -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; +using Artemis.Storage.Legacy.Entities.Profile; +using Artemis.Storage.Legacy.Entities.Profile.Conditions; +using Artemis.Storage.Legacy.Entities.Profile.Nodes; using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0022TransitionNodes : IStorageMigration +internal class M0022TransitionNodes : IStorageMigration { private void MigrateNodeScript(NodeScriptEntity? nodeScript) { diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0023LayoutProviders.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0023LayoutProviders.cs similarity index 88% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0023LayoutProviders.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0023LayoutProviders.cs index 602ba6f2e..1a8d7500b 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0023LayoutProviders.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0023LayoutProviders.cs @@ -1,8 +1,8 @@ using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0023LayoutProviders : IStorageMigration +internal class M0023LayoutProviders : IStorageMigration { public int UserVersion => 23; @@ -19,9 +19,13 @@ public void Apply(LiteRepository repository) bsonDocument.Add("LayoutParameter", new BsonValue(customLayoutPath.AsString)); } else if (bsonDocument.TryGetValue("DisableDefaultLayout", out BsonValue disableDefaultLayout) && disableDefaultLayout.AsBoolean) + { bsonDocument.Add("LayoutType", new BsonValue("None")); + } else + { bsonDocument.Add("LayoutType", new BsonValue("Default")); + } bsonDocument.Remove("CustomLayoutPath"); bsonDocument.Remove("DisableDefaultLayout"); diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0024NodeProviders.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0024NodeProviders.cs similarity index 93% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0024NodeProviders.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0024NodeProviders.cs index 37d61c5d5..4617ae6d1 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0024NodeProviders.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0024NodeProviders.cs @@ -1,8 +1,8 @@ using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0024NodeProviders : IStorageMigration +internal class M0024NodeProviders : IStorageMigration { public int UserVersion => 24; @@ -20,6 +20,7 @@ public void Apply(LiteRepository repository) categoriesToUpdate.Add(profileCategoryBson); } } + categoryCollection.Update(categoriesToUpdate); ILiteCollection collection = repository.Database.GetCollection("ProfileEntity"); @@ -28,15 +29,12 @@ public void Apply(LiteRepository repository) { BsonArray? folders = profileBson["Folders"]?.AsArray; BsonArray? layers = profileBson["Layers"]?.AsArray; - + if (folders != null) - { foreach (BsonValue folder in folders) MigrateProfileElement(folder.AsDocument); - } - + if (layers != null) - { foreach (BsonValue layer in layers) { MigrateProfileElement(layer.AsDocument); @@ -44,8 +42,7 @@ public void Apply(LiteRepository repository) MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument); MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument); } - } - + profilesToUpdate.Add(profileBson); } @@ -56,10 +53,8 @@ private void MigrateProfileElement(BsonDocument profileElement) { BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray; if (layerEffects != null) - { foreach (BsonValue layerEffect in layerEffects) MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument); - } BsonValue? displayCondition = profileElement["DisplayCondition"]; if (displayCondition != null) @@ -75,16 +70,12 @@ private void MigratePropertyGroup(BsonDocument? propertyGroup) BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray; if (properties != null) - { foreach (BsonValue property in properties) MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument); - } if (propertyGroups != null) - { foreach (BsonValue childPropertyGroup in propertyGroups) MigratePropertyGroup(childPropertyGroup.AsDocument); - } } private void MigrateNodeScript(BsonDocument? nodeScript) diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs similarity index 91% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs index bbb121956..bff6ff6ff 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs @@ -1,8 +1,8 @@ using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0025NodeProvidersProfileConfig : IStorageMigration +internal class M0025NodeProvidersProfileConfig : IStorageMigration { public int UserVersion => 25; @@ -20,13 +20,14 @@ public void Apply(LiteRepository repository) profile["Version"] = 2; MigrateNodeScript(profile["ActivationCondition"]?.AsDocument); } + toUpdate.Add(profileCategoryBson); } } - + categoryCollection.Update(toUpdate); } - + private void MigrateNodeScript(BsonDocument? nodeScript) { if (nodeScript == null || nodeScript.Keys.Count == 0) diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0026NodeStorage.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0026NodeStorage.cs similarity index 97% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0026NodeStorage.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0026NodeStorage.cs index 1212455bb..cbea0db8d 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0026NodeStorage.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0026NodeStorage.cs @@ -3,9 +3,9 @@ using LiteDB; using Serilog; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0026NodeStorage : IStorageMigration +internal class M0026NodeStorage : IStorageMigration { private readonly ILogger _logger; @@ -45,13 +45,10 @@ public void Apply(LiteRepository repository) BsonArray? layers = profileBson["Layers"]?.AsArray; if (folders != null) - { foreach (BsonValue folder in folders) MigrateProfileElement(folder.AsDocument); - } if (layers != null) - { foreach (BsonValue layer in layers) { MigrateProfileElement(layer.AsDocument); @@ -59,7 +56,6 @@ public void Apply(LiteRepository repository) MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument); MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument); } - } profilesToUpdate.Add(profileBson); } @@ -71,10 +67,8 @@ private void MigrateProfileElement(BsonDocument profileElement) { BsonArray? layerEffects = profileElement["LayerEffects"]?.AsArray; if (layerEffects != null) - { foreach (BsonValue layerEffect in layerEffects) MigratePropertyGroup(layerEffect.AsDocument["PropertyGroup"].AsDocument); - } BsonValue? displayCondition = profileElement["DisplayCondition"]; if (displayCondition != null) @@ -90,16 +84,12 @@ private void MigratePropertyGroup(BsonDocument? propertyGroup) BsonArray? propertyGroups = propertyGroup["PropertyGroups"]?.AsArray; if (properties != null) - { foreach (BsonValue property in properties) MigrateNodeScript(property.AsDocument["DataBinding"]?["NodeScript"]?.AsDocument); - } if (propertyGroups != null) - { foreach (BsonValue childPropertyGroup in propertyGroups) MigratePropertyGroup(childPropertyGroup.AsDocument); - } } private void MigrateNodeScript(BsonDocument? nodeScript) @@ -112,10 +102,8 @@ private void MigrateNodeScript(BsonDocument? nodeScript) return; foreach (BsonValue node in nodes) - { // Migrate the storage of the node node["Storage"] = MigrateNodeStorageJson(node.AsDocument["Storage"]?.AsString, _logger); - } } private static string? MigrateNodeStorageJson(string? json, ILogger logger) diff --git a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0027Namespace.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0027Namespace.cs similarity index 79% rename from src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0027Namespace.cs rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0027Namespace.cs index 6a98a25eb..b0bee99a1 100644 --- a/src/Artemis.Storage.Migrator/Legacy/Migrations/Storage/M0027Namespace.cs +++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0027Namespace.cs @@ -1,8 +1,8 @@ using LiteDB; -namespace Artemis.Storage.Migrator.Legacy.Migrations.Storage; +namespace Artemis.Storage.Legacy.Migrations.Storage; -public class M0027Namespace : IStorageMigration +internal class M0027Namespace : IStorageMigration { public int UserVersion => 27; @@ -25,21 +25,17 @@ private void MigrateDocument(BsonDocument document) foreach ((string? key, BsonValue? value) in document) { if (key == "_type") - { document[key] = document[key].AsString - .Replace("Artemis.Storage.Entities.Profile", "Artemis.Storage.Migrator.Legacy.Entities.Profile") - .Replace(", Artemis.Storage", ", Artemis.Storage.Migrator"); - } + .Replace("Artemis.Storage.Entities", "Artemis.Storage.Legacy.Entities") + .Replace(", Artemis.Storage", ", Artemis.Storage.Legacy"); else if (value.IsDocument) MigrateDocument(value.AsDocument); else if (value.IsArray) - { foreach (BsonValue bsonValue in value.AsArray) { if (bsonValue.IsDocument) MigrateDocument(bsonValue.AsDocument); } - } } } } \ No newline at end of file diff --git a/src/Artemis.Storage.Legacy/Program.cs b/src/Artemis.Storage.Legacy/Program.cs new file mode 100644 index 000000000..c225e03f5 --- /dev/null +++ b/src/Artemis.Storage.Legacy/Program.cs @@ -0,0 +1,22 @@ +// using Artemis.Core.DryIoc; +// using Artemis.Storage; +// using Artemis.Storage.Legacy; +// using DryIoc; +// using Microsoft.EntityFrameworkCore; +// using Serilog; +// +// using Container container = new(rules => rules +// .WithMicrosoftDependencyInjectionRules() +// .WithConcreteTypeDynamicRegistrations() +// .WithoutThrowOnRegisteringDisposableTransient()); +// +// container.RegisterCore(); +// +// ILogger logger = container.Resolve(); +// ArtemisDbContext dbContext = container.Resolve(); +// +// logger.Information("Applying pending migrations..."); +// dbContext.Database.Migrate(); +// logger.Information("Pending migrations applied"); +// +// MigrationService.MigrateToSqlite(logger, dbContext); \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs deleted file mode 100644 index 075f3ad9a..000000000 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/AdaptionHints/KeyboardSectionAdaptionHintEntity.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.AdaptionHints; - -public class KeyboardSectionAdaptionHintEntity : IAdaptionHintEntity -{ - public int Section { get; set; } -} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs deleted file mode 100644 index e644c4331..000000000 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/AlwaysOnConditionEntity.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; - -public class AlwaysOnConditionEntity : IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs deleted file mode 100644 index 82fb095b1..000000000 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/PlayOnceConditionEntity.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; - -public class PlayOnceConditionEntity : IConditionEntity; \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs deleted file mode 100644 index 065bd92bd..000000000 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/Conditions/StaticConditionEntity.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; - -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.Conditions; - -public class StaticConditionEntity : IConditionEntity -{ - public int PlayMode { get; set; } - public int StopMode { get; set; } - public NodeScriptEntity? Script { get; set; } -} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs deleted file mode 100644 index 74aa3851e..000000000 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/DataBindings/DataBindingEntity.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Artemis.Storage.Migrator.Legacy.Entities.Profile.Nodes; - -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile.DataBindings; - -public class DataBindingEntity -{ - public bool IsEnabled { get; set; } - public NodeScriptEntity? NodeScript { get; set; } -} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs b/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs deleted file mode 100644 index a0572cc91..000000000 --- a/src/Artemis.Storage.Migrator/Legacy/Entities/Profile/ProfileConfigurationHotkeyEntity.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Artemis.Storage.Migrator.Legacy.Entities.Profile; - -public class ProfileConfigurationHotkeyEntity -{ - public int? Key { get; set; } - public int? Modifiers { get; set; } -} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs b/src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs deleted file mode 100644 index 86f4f1384..000000000 --- a/src/Artemis.Storage.Migrator/Legacy/StorageMigrationService.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Artemis.Storage.Migrator.Legacy.Migrations; -using LiteDB; -using Serilog; - -namespace Artemis.Storage.Migrator.Legacy; - -public static class StorageMigrationService -{ - public static void ApplyPendingMigrations(ILogger logger, LiteRepository repository, IList migrations) - { - foreach (IStorageMigration storageMigration in migrations.OrderBy(m => m.UserVersion)) - { - if (repository.Database.UserVersion >= storageMigration.UserVersion) - continue; - - logger.Information("Applying storage migration {storageMigration} to update DB from v{oldVersion} to v{newVersion}", - storageMigration.GetType().Name, repository.Database.UserVersion, storageMigration.UserVersion); - - repository.Database.BeginTrans(); - try - { - storageMigration.Apply(repository); - } - catch (Exception) - { - repository.Database.Rollback(); - throw; - } - - repository.Database.Commit(); - repository.Database.UserVersion = storageMigration.UserVersion; - } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/Program.cs b/src/Artemis.Storage.Migrator/Program.cs deleted file mode 100644 index ba15b7bfa..000000000 --- a/src/Artemis.Storage.Migrator/Program.cs +++ /dev/null @@ -1,142 +0,0 @@ -using Artemis.Core; -using Artemis.Core.DryIoc; -using Artemis.Storage.Migrator.Legacy; -using Artemis.Storage.Migrator.Legacy.Entities.General; -using Artemis.Storage.Migrator.Legacy.Entities.Plugins; -using Artemis.Storage.Migrator.Legacy.Entities.Profile; -using Artemis.Storage.Migrator.Legacy.Entities.Surface; -using Artemis.Storage.Migrator.Legacy.Entities.Workshop; -using Artemis.Storage.Migrator.Legacy.Migrations.Storage; -using DryIoc; -using LiteDB; -using Microsoft.EntityFrameworkCore; -using Serilog; - -namespace Artemis.Storage.Migrator; - -class Program -{ - static void Main(string[] args) - { - using Container container = new(rules => rules - .WithMicrosoftDependencyInjectionRules() - .WithConcreteTypeDynamicRegistrations() - .WithoutThrowOnRegisteringDisposableTransient()); - - container.RegisterCore(); - - ILogger logger = container.Resolve(); - ArtemisDbContext dbContext = container.Resolve(); - logger.Information("Applying pending migrations..."); - dbContext.Database.Migrate(); - logger.Information("Pending migrations applied"); - - if (!File.Exists(Path.Combine(Constants.DataFolder, "database.db"))) - { - logger.Information("No legacy database found, nothing to migrate"); - return; - } - - logger.Information("Migrating legacy database..."); - - try - { - MigrateLegacyDatabase(logger, dbContext); - // After a successful migration, keep the legacy database around for a while - File.Move(Path.Combine(Constants.DataFolder, "database.db"), Path.Combine(Constants.DataFolder, "legacy.db")); - } - catch (Exception e) - { - logger.Error(e, "Failed to migrate legacy database"); - throw; - } - finally - { - File.Delete(Path.Combine(Constants.DataFolder, "temp.db")); - } - - logger.Information("Legacy database migrated"); - } - - private static void MigrateLegacyDatabase(ILogger logger, ArtemisDbContext dbContext) - { - // Copy the database before using it, we're going to make some modifications to it and we don't want to mess up the original - string databasePath = Path.Combine(Constants.DataFolder, "database.db"); - string tempPath = Path.Combine(Constants.DataFolder, "temp.db"); - File.Copy(databasePath, tempPath, true); - - using LiteRepository repository = new($"FileName={tempPath}"); - - // Apply pending LiteDB migrations, this includes a migration that transforms namespaces to Artemis.Storage.Migrator - StorageMigrationService.ApplyPendingMigrations( - logger, - repository, - [ - new M0020AvaloniaReset(), - new M0021GradientNodes(), - new M0022TransitionNodes(), - new M0023LayoutProviders(), - new M0024NodeProviders(), - new M0025NodeProvidersProfileConfig(), - new M0026NodeStorage(logger), - new M0027Namespace(), - ] - ); - - // Devices - if (!dbContext.Devices.Any()) - { - logger.Information("Migrating devices"); - List legacyDevices = repository.Query().Include(s => s.InputIdentifiers).ToList(); - dbContext.Devices.AddRange(legacyDevices.Select(l => l.Migrate())); - dbContext.SaveChanges(); - } - - // Entries - if (!dbContext.Entries.Any()) - { - logger.Information("Migrating entries"); - List legacyEntries = repository.Query().ToList(); - dbContext.Entries.AddRange(legacyEntries.Select(l => l.Migrate())); - dbContext.SaveChanges(); - } - - // Plugins - if (!dbContext.Plugins.Any()) - { - logger.Information("Migrating plugins"); - List legacyPlugins = repository.Query().ToList(); - dbContext.Plugins.AddRange(legacyPlugins.Select(l => l.Migrate())); - dbContext.SaveChanges(); - } - - // PluginSettings - if (!dbContext.PluginSettings.Any()) - { - logger.Information("Migrating plugin settings"); - List legacyPluginSettings = repository.Query().ToList(); - dbContext.PluginSettings.AddRange(legacyPluginSettings.Select(l => l.Migrate())); - dbContext.SaveChanges(); - } - - // ProfileCategories - if (!dbContext.ProfileCategories.Any()) - { - logger.Information("Migrating profile categories"); - List legacyProfileCategories = repository.Query().ToList(); - ILiteStorage profileIcons = repository.Database.GetStorage("profileIcons"); - List legacyProfiles = repository.Query().ToList(); - dbContext.ProfileCategories.AddRange(legacyProfileCategories.Select(l => l.Migrate(logger, legacyProfiles, profileIcons))); - dbContext.SaveChanges(); - } - - // Releases - if (!dbContext.Releases.Any()) - { - logger.Information("Migrating releases"); - List legacyReleases = repository.Query().ToList(); - dbContext.Releases.AddRange(legacyReleases.Select(l => l.Migrate())); - dbContext.SaveChanges(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/artemis.db b/src/Artemis.Storage.Migrator/artemis.db deleted file mode 100644 index 286e00251800998db3fc3ad17b0ae79573adf4ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86016 zcmeI&&u`mg7{Kwk?UMdj+x_Td(6q@!LRzG;{zB>s7t+)%*3zUT-Dp85GL73=WJ!sg zK@S|#F%J9%+&S}iaOcJW2`(J?3pnyRvE4d(W0d2pZ`3G>{l3ooe4fXz-x{;cgNPVPfsZ?r0Jnx8S@R$>Crh*URRk`wh)Z2;FqmzH;7ynHyrG8B< z{I>YZ_34G5XKQo6&-^sIGTol}JhL#poPLo0W6Het*TloACFOh3aQsL2gQbjmys308 zyIHrsH4bb0?beCiunMiFYuZh#tv6lUJr6gVu65dKpBr^~yE}!7oF?6Q9oT*x_jZ^8=X_Txnr4br)>oz?``XU5W^mBu{1p9 z;YPK7$KWvjXouVNh8p?CZ0?nHGox;9DnD4R`DMeB$0*-=%OBf8eyga*@XXI@;*mA# zS?xeSJkUyI@qAh=YK3yCdQiz5r32CXNtniHcC*@k#n{VNjqT9ZdLdse~322j^#&Ja1XU(VJe$i?0$DsPF2P;aZBGEPi!}()BB2F!#fQ@=r~X?tw!Mb-l)#YQiU6{}&Hj^zZe3(^^Y^f_lV9oKA#?Bv}bf5KhFzPylD3-?CF9(-eMHF&ppaepqOE-x#uzKYCn z;b{}pc(~zuH147OV*p6{N+e3=i_t||Xf+z}dBl?kD0xvt$jb^4_je3~85?GlL!Pz93*kjAuel?+zj^bM?6HyKT#A#x>XylSSPA znDZ~l`u_8C#}*+J&GS}AT=Pa1S+R9`wOL>G@+V2Q-~a0Go}r!lW?NiwT<>x))V&Z+ z>cP%|>NJ{X9oMK^;@0cL7MJk2W_#w@nRf*kXyaA#R9q$drBz$?-(2e+Dj9WSL%AsT zrkq+$-`TTI`#;*9k8Q^lw|&x$6VWP{wz;hN@R;6N9;w;EdzBFflAH?SB&c_eUHPOS zB#eo)`o+em$yfS4wN>fnNpXuk?cHY2i>I>qXG;9>KmY**5I_I{1Q0*~0R#|00D(7D z;Jz}ssjjW8y}z=)@_21!eRFNKW;AWrHXDy0tvp(r6>%wxf2YJB4+IcE009ILKmY** z5I_I{1Q2+m1>TMaCAbPO?91|e|2q1s}0tg_000IagfB*ul z|5F19Ab Devices => Set(); public DbSet Entries => Set(); public DbSet Plugins => Set(); + public DbSet PluginFeatures => Set(); public DbSet PluginSettings => Set(); public DbSet ProfileCategories => Set(); + public DbSet ProfileContainers => Set(); public DbSet Releases => Set(); public string DataFolder { get; set; } = string.Empty; diff --git a/src/Artemis.Storage/Entities/General/ReleaseEntity.cs b/src/Artemis.Storage/Entities/General/ReleaseEntity.cs index f83efb6f1..47619b3c7 100644 --- a/src/Artemis.Storage/Entities/General/ReleaseEntity.cs +++ b/src/Artemis.Storage/Entities/General/ReleaseEntity.cs @@ -1,11 +1,17 @@ using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Entities.General; +[Index(nameof(Version), IsUnique = true)] +[Index(nameof(InstalledAt))] public class ReleaseEntity { public Guid Id { get; set; } + [MaxLength(64)] public string Version { get; set; } = string.Empty; + public DateTimeOffset? InstalledAt { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs index 0a0ca3b16..faf43f951 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Entities.Plugins; /// /// Represents the configuration of a plugin, each plugin has one configuration /// +[Index(nameof(PluginGuid), IsUnique = true)] public class PluginEntity { public PluginEntity() @@ -14,6 +17,7 @@ public PluginEntity() } public Guid Id { get; set; } + public Guid PluginGuid { get; set; } public bool IsEnabled { get; set; } public List Features { get; set; } @@ -25,7 +29,7 @@ public PluginEntity() public class PluginFeatureEntity { public Guid Id { get; set; } - + public string Type { get; set; } = string.Empty; public bool IsEnabled { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs index 1d604c28a..b386b34d6 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs @@ -1,15 +1,20 @@ using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Entities.Plugins; /// /// Represents the setting of a plugin, a plugin can have multiple settings /// +[Index(nameof(Name), nameof(PluginGuid), IsUnique = true)] +[Index(nameof(PluginGuid))] public class PluginSettingEntity { public Guid Id { get; set; } public Guid PluginGuid { get; set; } + [MaxLength(128)] public string Name { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs index 02a797011..b7a3c15f4 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs @@ -1,12 +1,16 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Entities.Profile; +[Index(nameof(Name), IsUnique = true)] public class ProfileCategoryEntity { public Guid Id { get; set; } + [MaxLength(64)] public string Name { get; set; } = string.Empty; public bool IsCollapsed { get; set; } public bool IsSuspended { get; set; } diff --git a/src/Artemis.Storage/Entities/RawProfileContainer.cs b/src/Artemis.Storage/Entities/RawProfileContainer.cs new file mode 100644 index 000000000..4638f3e29 --- /dev/null +++ b/src/Artemis.Storage/Entities/RawProfileContainer.cs @@ -0,0 +1,10 @@ +using System; + +namespace Artemis.Storage.Entities; + +internal class RawProfileContainer +{ + public Guid Id { get; set; } + public string ProfileConfiguration { get; set; } + public string Profile { get; set; } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs index 374a20941..6500b9b1d 100644 --- a/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs +++ b/src/Artemis.Storage/Entities/Surface/DeviceEntity.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; namespace Artemis.Storage.Entities.Surface; @@ -11,8 +12,12 @@ public DeviceEntity() Categories = new List(); } + [MaxLength(512)] public string Id { get; set; } = string.Empty; + + [MaxLength(512)] public string DeviceProvider { get; set; } = string.Empty; + public float X { get; set; } public float Y { get; set; } public float Rotation { get; set; } @@ -22,10 +27,16 @@ public DeviceEntity() public float GreenScale { get; set; } public float BlueScale { get; set; } public bool IsEnabled { get; set; } - + public int PhysicalLayout { get; set; } + + [MaxLength(32)] public string? LogicalLayout { get; set; } + + [MaxLength(64)] public string? LayoutType { get; set; } + + [MaxLength(512)] public string? LayoutParameter { get; set; } public List InputIdentifiers { get; set; } diff --git a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs index cd828721c..37cdd4cfa 100644 --- a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs @@ -1,22 +1,23 @@ using System; using System.Collections.Generic; -using System.Text.Json.Nodes; +using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Entities.Workshop; +[Index(nameof(EntryId), IsUnique = true)] public class EntryEntity { public Guid Id { get; set; } - + public long EntryId { get; set; } public int EntryType { get; set; } - - public string Author { get; set; } = string.Empty; + + public string Author { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public long ReleaseId { get; set; } public string ReleaseVersion { get; set; } = string.Empty; public DateTimeOffset InstalledAt { get; set; } - public Dictionary Metadata { get; set; } + public Dictionary? Metadata { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Exceptions/ArtemisStorageException.cs b/src/Artemis.Storage/Exceptions/ArtemisStorageException.cs new file mode 100644 index 000000000..85e08a3df --- /dev/null +++ b/src/Artemis.Storage/Exceptions/ArtemisStorageException.cs @@ -0,0 +1,14 @@ +using System; + +namespace Artemis.Storage.Exceptions; + +public class ArtemisStorageException : Exception +{ + public ArtemisStorageException(string message) : base(message) + { + } + + public ArtemisStorageException(string message, Exception innerException) : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs b/src/Artemis.Storage/Migrations/20240310201706_Initial.Designer.cs similarity index 90% rename from src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs rename to src/Artemis.Storage/Migrations/20240310201706_Initial.Designer.cs index 14b41beeb..bb62ee588 100644 --- a/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs +++ b/src/Artemis.Storage/Migrations/20240310201706_Initial.Designer.cs @@ -11,7 +11,7 @@ namespace Artemis.Storage.Migrations { [DbContext(typeof(ArtemisDbContext))] - [Migration("20240308203921_Initial")] + [Migration("20240310201706_Initial")] partial class Initial { /// @@ -31,10 +31,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Version") .IsRequired() + .HasMaxLength(64) .HasColumnType("TEXT"); b.HasKey("Id"); + b.HasIndex("InstalledAt"); + + b.HasIndex("Version") + .IsUnique(); + b.ToTable("Releases"); }); @@ -47,8 +53,14 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("IsEnabled") .HasColumnType("INTEGER"); + b.Property("PluginGuid") + .HasColumnType("TEXT"); + b.HasKey("Id"); + b.HasIndex("PluginGuid") + .IsUnique(); + b.ToTable("Plugins"); }); @@ -72,7 +84,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("PluginEntityId"); - b.ToTable("PluginFeatureEntity"); + b.ToTable("PluginFeatures"); }); modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b => @@ -83,6 +95,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() + .HasMaxLength(128) .HasColumnType("TEXT"); b.Property("PluginGuid") @@ -94,6 +107,11 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("PluginGuid"); + + b.HasIndex("Name", "PluginGuid") + .IsUnique(); + b.ToTable("PluginSettings"); }); @@ -111,6 +129,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() + .HasMaxLength(64) .HasColumnType("TEXT"); b.Property("Order") @@ -118,6 +137,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + b.ToTable("ProfileCategories"); }); @@ -146,12 +168,13 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("ProfileCategoryId"); - b.ToTable("ProfileContainerEntity"); + b.ToTable("ProfileContainers"); }); modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => { b.Property("Id") + .HasMaxLength(512) .HasColumnType("TEXT"); b.Property("BlueScale") @@ -163,6 +186,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("DeviceProvider") .IsRequired() + .HasMaxLength(512) .HasColumnType("TEXT"); b.Property("GreenScale") @@ -172,12 +196,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("LayoutParameter") + .HasMaxLength(512) .HasColumnType("TEXT"); b.Property("LayoutType") + .HasMaxLength(64) .HasColumnType("TEXT"); b.Property("LogicalLayout") + .HasMaxLength(32) .HasColumnType("TEXT"); b.Property("PhysicalLayout") @@ -226,7 +253,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Metadata") - .IsRequired() .HasColumnType("TEXT"); b.Property("Name") @@ -242,6 +268,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("EntryId") + .IsUnique(); + b.ToTable("Entries"); }); diff --git a/src/Artemis.Storage/Migrations/20240308203921_Initial.cs b/src/Artemis.Storage/Migrations/20240310201706_Initial.cs similarity index 74% rename from src/Artemis.Storage/Migrations/20240308203921_Initial.cs rename to src/Artemis.Storage/Migrations/20240310201706_Initial.cs index 607c92cc6..286f29922 100644 --- a/src/Artemis.Storage/Migrations/20240308203921_Initial.cs +++ b/src/Artemis.Storage/Migrations/20240310201706_Initial.cs @@ -15,8 +15,8 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "Devices", columns: table => new { - Id = table.Column(type: "TEXT", nullable: false), - DeviceProvider = table.Column(type: "TEXT", nullable: false), + Id = table.Column(type: "TEXT", maxLength: 512, nullable: false), + DeviceProvider = table.Column(type: "TEXT", maxLength: 512, nullable: false), X = table.Column(type: "REAL", nullable: false), Y = table.Column(type: "REAL", nullable: false), Rotation = table.Column(type: "REAL", nullable: false), @@ -27,9 +27,9 @@ protected override void Up(MigrationBuilder migrationBuilder) BlueScale = table.Column(type: "REAL", nullable: false), IsEnabled = table.Column(type: "INTEGER", nullable: false), PhysicalLayout = table.Column(type: "INTEGER", nullable: false), - LogicalLayout = table.Column(type: "TEXT", nullable: true), - LayoutType = table.Column(type: "TEXT", nullable: true), - LayoutParameter = table.Column(type: "TEXT", nullable: true), + LogicalLayout = table.Column(type: "TEXT", maxLength: 32, nullable: true), + LayoutType = table.Column(type: "TEXT", maxLength: 64, nullable: true), + LayoutParameter = table.Column(type: "TEXT", maxLength: 512, nullable: true), Categories = table.Column(type: "TEXT", nullable: false), InputIdentifiers = table.Column(type: "TEXT", nullable: false), InputMappings = table.Column(type: "TEXT", nullable: false) @@ -51,7 +51,7 @@ protected override void Up(MigrationBuilder migrationBuilder) ReleaseId = table.Column(type: "INTEGER", nullable: false), ReleaseVersion = table.Column(type: "TEXT", nullable: false), InstalledAt = table.Column(type: "TEXT", nullable: false), - Metadata = table.Column(type: "TEXT", nullable: false) + Metadata = table.Column(type: "TEXT", nullable: true) }, constraints: table => { @@ -63,6 +63,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "TEXT", nullable: false), + PluginGuid = table.Column(type: "TEXT", nullable: false), IsEnabled = table.Column(type: "INTEGER", nullable: false) }, constraints: table => @@ -76,7 +77,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { Id = table.Column(type: "TEXT", nullable: false), PluginGuid = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), Value = table.Column(type: "TEXT", nullable: false) }, constraints: table => @@ -89,7 +90,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 64, nullable: false), IsCollapsed = table.Column(type: "INTEGER", nullable: false), IsSuspended = table.Column(type: "INTEGER", nullable: false), Order = table.Column(type: "INTEGER", nullable: false) @@ -104,7 +105,7 @@ protected override void Up(MigrationBuilder migrationBuilder) columns: table => new { Id = table.Column(type: "TEXT", nullable: false), - Version = table.Column(type: "TEXT", nullable: false), + Version = table.Column(type: "TEXT", maxLength: 64, nullable: false), InstalledAt = table.Column(type: "TEXT", nullable: true) }, constraints: table => @@ -113,7 +114,7 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateTable( - name: "PluginFeatureEntity", + name: "PluginFeatures", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), @@ -123,16 +124,16 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_PluginFeatureEntity", x => x.Id); + table.PrimaryKey("PK_PluginFeatures", x => x.Id); table.ForeignKey( - name: "FK_PluginFeatureEntity_Plugins_PluginEntityId", + name: "FK_PluginFeatures_Plugins_PluginEntityId", column: x => x.PluginEntityId, principalTable: "Plugins", principalColumn: "Id"); }); migrationBuilder.CreateTable( - name: "ProfileContainerEntity", + name: "ProfileContainers", columns: table => new { Id = table.Column(type: "TEXT", nullable: false), @@ -143,9 +144,9 @@ protected override void Up(MigrationBuilder migrationBuilder) }, constraints: table => { - table.PrimaryKey("PK_ProfileContainerEntity", x => x.Id); + table.PrimaryKey("PK_ProfileContainers", x => x.Id); table.ForeignKey( - name: "FK_ProfileContainerEntity_ProfileCategories_ProfileCategoryId", + name: "FK_ProfileContainers_ProfileCategories_ProfileCategoryId", column: x => x.ProfileCategoryId, principalTable: "ProfileCategories", principalColumn: "Id", @@ -153,14 +154,54 @@ protected override void Up(MigrationBuilder migrationBuilder) }); migrationBuilder.CreateIndex( - name: "IX_PluginFeatureEntity_PluginEntityId", - table: "PluginFeatureEntity", + name: "IX_Entries_EntryId", + table: "Entries", + column: "EntryId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PluginFeatures_PluginEntityId", + table: "PluginFeatures", column: "PluginEntityId"); migrationBuilder.CreateIndex( - name: "IX_ProfileContainerEntity_ProfileCategoryId", - table: "ProfileContainerEntity", + name: "IX_Plugins_PluginGuid", + table: "Plugins", + column: "PluginGuid", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PluginSettings_Name_PluginGuid", + table: "PluginSettings", + columns: new[] { "Name", "PluginGuid" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PluginSettings_PluginGuid", + table: "PluginSettings", + column: "PluginGuid"); + + migrationBuilder.CreateIndex( + name: "IX_ProfileCategories_Name", + table: "ProfileCategories", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ProfileContainers_ProfileCategoryId", + table: "ProfileContainers", column: "ProfileCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Releases_InstalledAt", + table: "Releases", + column: "InstalledAt"); + + migrationBuilder.CreateIndex( + name: "IX_Releases_Version", + table: "Releases", + column: "Version", + unique: true); } /// @@ -173,13 +214,13 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Entries"); migrationBuilder.DropTable( - name: "PluginFeatureEntity"); + name: "PluginFeatures"); migrationBuilder.DropTable( name: "PluginSettings"); migrationBuilder.DropTable( - name: "ProfileContainerEntity"); + name: "ProfileContainers"); migrationBuilder.DropTable( name: "Releases"); diff --git a/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs index bd8df8c96..270d88797 100644 --- a/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs +++ b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs @@ -28,10 +28,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Version") .IsRequired() + .HasMaxLength(64) .HasColumnType("TEXT"); b.HasKey("Id"); + b.HasIndex("InstalledAt"); + + b.HasIndex("Version") + .IsUnique(); + b.ToTable("Releases"); }); @@ -44,8 +50,14 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsEnabled") .HasColumnType("INTEGER"); + b.Property("PluginGuid") + .HasColumnType("TEXT"); + b.HasKey("Id"); + b.HasIndex("PluginGuid") + .IsUnique(); + b.ToTable("Plugins"); }); @@ -69,7 +81,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("PluginEntityId"); - b.ToTable("PluginFeatureEntity"); + b.ToTable("PluginFeatures"); }); modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b => @@ -80,6 +92,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() + .HasMaxLength(128) .HasColumnType("TEXT"); b.Property("PluginGuid") @@ -91,6 +104,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("PluginGuid"); + + b.HasIndex("Name", "PluginGuid") + .IsUnique(); + b.ToTable("PluginSettings"); }); @@ -108,6 +126,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() + .HasMaxLength(64) .HasColumnType("TEXT"); b.Property("Order") @@ -115,6 +134,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + b.ToTable("ProfileCategories"); }); @@ -143,12 +165,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ProfileCategoryId"); - b.ToTable("ProfileContainerEntity"); + b.ToTable("ProfileContainers"); }); modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => { b.Property("Id") + .HasMaxLength(512) .HasColumnType("TEXT"); b.Property("BlueScale") @@ -160,6 +183,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("DeviceProvider") .IsRequired() + .HasMaxLength(512) .HasColumnType("TEXT"); b.Property("GreenScale") @@ -169,12 +193,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("INTEGER"); b.Property("LayoutParameter") + .HasMaxLength(512) .HasColumnType("TEXT"); b.Property("LayoutType") + .HasMaxLength(64) .HasColumnType("TEXT"); b.Property("LogicalLayout") + .HasMaxLength(32) .HasColumnType("TEXT"); b.Property("PhysicalLayout") @@ -223,7 +250,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("TEXT"); b.Property("Metadata") - .IsRequired() .HasColumnType("TEXT"); b.Property("Name") @@ -239,6 +265,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("EntryId") + .IsUnique(); + b.ToTable("Entries"); }); diff --git a/src/Artemis.Storage/Repositories/DeviceRepository.cs b/src/Artemis.Storage/Repositories/DeviceRepository.cs index 28387fa6e..2eb7098c2 100644 --- a/src/Artemis.Storage/Repositories/DeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/DeviceRepository.cs @@ -1,43 +1,50 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Artemis.Storage.Entities.Surface; using Artemis.Storage.Repositories.Interfaces; namespace Artemis.Storage.Repositories; -internal class DeviceRepository : IDeviceRepository +internal class DeviceRepository(Func getContext) : IDeviceRepository { - private readonly ArtemisDbContext _dbContext; - - public DeviceRepository(ArtemisDbContext dbContext) - { - _dbContext = dbContext; - } - public void Add(DeviceEntity deviceEntity) { - _dbContext.Devices.Add(deviceEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.Devices.Add(deviceEntity); + dbContext.SaveChanges(); } public void Remove(DeviceEntity deviceEntity) { - _dbContext.Devices.Remove(deviceEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.Devices.Remove(deviceEntity); + dbContext.SaveChanges(); } public DeviceEntity? Get(string id) { - return _dbContext.Devices.FirstOrDefault(d => d.Id == id); + using ArtemisDbContext dbContext = getContext(); + return dbContext.Devices.FirstOrDefault(d => d.Id == id); } public IEnumerable GetAll() { - return _dbContext.Devices; + using ArtemisDbContext dbContext = getContext(); + return dbContext.Devices; } - - public void SaveChanges() + + public void Save(DeviceEntity deviceEntity) + { + using ArtemisDbContext dbContext = getContext(); + dbContext.Update(deviceEntity); + dbContext.SaveChanges(); + } + + public void SaveRange(IEnumerable deviceEntities) { - _dbContext.SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.UpdateRange(deviceEntities); + dbContext.SaveChanges(); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/EntryRepository.cs b/src/Artemis.Storage/Repositories/EntryRepository.cs index e378ace28..a901e637d 100644 --- a/src/Artemis.Storage/Repositories/EntryRepository.cs +++ b/src/Artemis.Storage/Repositories/EntryRepository.cs @@ -6,44 +6,44 @@ namespace Artemis.Storage.Repositories; -internal class EntryRepository : IEntryRepository +internal class EntryRepository(Func getContext) : IEntryRepository { - private readonly ArtemisDbContext _dbContext; - - public EntryRepository(ArtemisDbContext dbContext) - { - _dbContext = dbContext; - } - public void Add(EntryEntity entryEntity) { - _dbContext.Entries.Add(entryEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.Entries.Add(entryEntity); + dbContext.SaveChanges(); } public void Remove(EntryEntity entryEntity) { - _dbContext.Entries.Remove(entryEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.Entries.Remove(entryEntity); + dbContext.SaveChanges(); } public EntryEntity? Get(Guid id) { - return _dbContext.Entries.FirstOrDefault(s => s.Id == id); + using ArtemisDbContext dbContext = getContext(); + return dbContext.Entries.FirstOrDefault(s => s.Id == id); } public EntryEntity? GetByEntryId(long entryId) { - return _dbContext.Entries.FirstOrDefault(s => s.EntryId == entryId); + using ArtemisDbContext dbContext = getContext(); + return dbContext.Entries.FirstOrDefault(s => s.EntryId == entryId); } public IEnumerable GetAll() { - return _dbContext.Entries; + using ArtemisDbContext dbContext = getContext(); + return dbContext.Entries; } - - public void SaveChanges() + + public void Save(EntryEntity entryEntity) { - _dbContext.SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.Update(entryEntity); + dbContext.SaveChanges(); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs index 960f61d66..f2a77e3d2 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs @@ -9,5 +9,6 @@ public interface IDeviceRepository : IRepository void Remove(DeviceEntity deviceEntity); DeviceEntity? Get(string id); IEnumerable GetAll(); - void SaveChanges(); + void Save(DeviceEntity deviceEntity); + void SaveRange(IEnumerable deviceEntities); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs index f1146de9f..46722068e 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs @@ -11,5 +11,5 @@ public interface IEntryRepository : IRepository EntryEntity? Get(Guid id); EntryEntity? GetByEntryId(long entryId); IEnumerable GetAll(); - void SaveChanges(); + void Save(EntryEntity entryEntity); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs index 87e3e48e4..cb25caf5d 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs @@ -5,10 +5,9 @@ namespace Artemis.Storage.Repositories.Interfaces; public interface IPluginRepository : IRepository { - void AddPlugin(PluginEntity pluginEntity); - PluginEntity? GetPluginByGuid(Guid pluginGuid); - void AddSetting(PluginSettingEntity pluginSettingEntity); + PluginEntity? GetPluginByPluginGuid(Guid pluginGuid); + void SaveSetting(PluginSettingEntity pluginSettingEntity); + void SavePlugin(PluginEntity pluginEntity); PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid); void RemoveSettings(Guid pluginGuid); - void SaveChanges(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs index cf3dccee6..533e8990e 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs @@ -12,5 +12,6 @@ public interface IProfileCategoryRepository : IRepository List GetAll(); ProfileCategoryEntity? Get(Guid id); bool IsUnique(string name, Guid? id); - void SaveChanges(); + void Save(ProfileCategoryEntity profileCategoryEntity); + void SaveRange(List profileCategoryEntities); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs new file mode 100644 index 000000000..bc26090a5 --- /dev/null +++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Text.Json.Nodes; +using Artemis.Storage.Entities.Profile; + +namespace Artemis.Storage.Repositories.Interfaces; + +public interface IProfileRepository : IRepository +{ + void Add(ProfileContainerEntity profileContainerEntity); + void Remove(ProfileContainerEntity profileContainerEntity); + void Save(ProfileContainerEntity profileContainerEntity); + void SaveRange(List profileContainerEntities); + void MigrateProfiles(); + void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson); +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IReleaseRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IReleaseRepository.cs new file mode 100644 index 000000000..9404bf8dd --- /dev/null +++ b/src/Artemis.Storage/Repositories/Interfaces/IReleaseRepository.cs @@ -0,0 +1,9 @@ +using Artemis.Storage.Entities.General; + +namespace Artemis.Storage.Repositories.Interfaces; + +public interface IReleaseRepository : IRepository +{ + bool SaveVersionInstallDate(string version); + ReleaseEntity? GetPreviousInstalledVersion(); +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/PluginRepository.cs b/src/Artemis.Storage/Repositories/PluginRepository.cs index 8c53d776c..abb335893 100644 --- a/src/Artemis.Storage/Repositories/PluginRepository.cs +++ b/src/Artemis.Storage/Repositories/PluginRepository.cs @@ -6,44 +6,39 @@ namespace Artemis.Storage.Repositories; -internal class PluginRepository : IPluginRepository +internal class PluginRepository(Func getContext) : IPluginRepository { - private readonly ArtemisDbContext _dbContext; - - public PluginRepository(ArtemisDbContext dbContext) + public PluginEntity? GetPluginByPluginGuid(Guid pluginGuid) { - _dbContext = dbContext; + using ArtemisDbContext dbContext = getContext(); + return dbContext.Plugins.Include(p => p.Features).FirstOrDefault(p => p.PluginGuid == pluginGuid); } - public void AddPlugin(PluginEntity pluginEntity) + public PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid) { - _dbContext.Plugins.Add(pluginEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + return dbContext.PluginSettings.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid); } - public PluginEntity? GetPluginByGuid(Guid pluginGuid) + public void RemoveSettings(Guid pluginGuid) { - return _dbContext.Plugins.Include(p => p.Features).FirstOrDefault(p => p.Id == pluginGuid); + using ArtemisDbContext dbContext = getContext(); + dbContext.PluginSettings.RemoveRange(dbContext.PluginSettings.Where(s => s.PluginGuid == pluginGuid)); + dbContext.SaveChanges(); } - public void AddSetting(PluginSettingEntity pluginSettingEntity) + public void SaveSetting(PluginSettingEntity pluginSettingEntity) { - _dbContext.PluginSettings.Add(pluginSettingEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.PluginSettings.Update(pluginSettingEntity); + dbContext.SaveChanges(); } - public PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid) - { - return _dbContext.PluginSettings.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid); - } - - public void RemoveSettings(Guid pluginGuid) - { - _dbContext.PluginSettings.RemoveRange(_dbContext.PluginSettings.Where(s => s.PluginGuid == pluginGuid)); - } - - public void SaveChanges() + public void SavePlugin(PluginEntity pluginEntity) { - _dbContext.SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.Update(pluginEntity); + dbContext.SaveChanges(); } + } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs index 7d0ef11bc..90e53c731 100644 --- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs @@ -7,47 +7,63 @@ namespace Artemis.Storage.Repositories; -internal class ProfileCategoryRepository : IProfileCategoryRepository +internal class ProfileCategoryRepository(Func getContext, IProfileRepository profileRepository) : IProfileCategoryRepository { - private readonly ArtemisDbContext _dbContext; - - public ProfileCategoryRepository(ArtemisDbContext dbContext) - { - _dbContext = dbContext; - } + private bool _migratedProfiles; public void Add(ProfileCategoryEntity profileCategoryEntity) { - _dbContext.ProfileCategories.Add(profileCategoryEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.ProfileCategories.Add(profileCategoryEntity); + dbContext.SaveChanges(); } public void Remove(ProfileCategoryEntity profileCategoryEntity) { - _dbContext.ProfileCategories.Remove(profileCategoryEntity); - SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.ProfileCategories.Remove(profileCategoryEntity); + dbContext.SaveChanges(); } public List GetAll() { - return _dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).ToList(); + if (!_migratedProfiles) + { + profileRepository.MigrateProfiles(); + _migratedProfiles = true; + } + + using ArtemisDbContext dbContext = getContext(); + return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).ToList(); } public ProfileCategoryEntity? Get(Guid id) { - return _dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).FirstOrDefault(c => c.Id == id); + using ArtemisDbContext dbContext = getContext(); + return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).FirstOrDefault(c => c.Id == id); } - public bool IsUnique(string name, Guid? id) + public void Save(ProfileCategoryEntity profileCategoryEntity) { - name = name.Trim(); - if (id == null) - return _dbContext.ProfileCategories.Any(p => p.Name == name); - return _dbContext.ProfileCategories.Any(p => p.Name == name && p.Id != id.Value); + using ArtemisDbContext dbContext = getContext(); + dbContext.Update(profileCategoryEntity); + dbContext.SaveChanges(); } - - public void SaveChanges() + + public void SaveRange(List profileCategoryEntities) { - _dbContext.SaveChanges(); + using ArtemisDbContext dbContext = getContext(); + dbContext.UpdateRange(profileCategoryEntities); + dbContext.SaveChanges(); + } + + public bool IsUnique(string name, Guid? id) + { + using ArtemisDbContext dbContext = getContext(); + + name = name.Trim(); + return id == null + ? dbContext.ProfileCategories.Any(p => p.Name == name) + : dbContext.ProfileCategories.Any(p => p.Name == name && p.Id != id.Value); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs new file mode 100644 index 000000000..78c9ca6a0 --- /dev/null +++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; +using Artemis.Storage.Entities; +using Artemis.Storage.Entities.Profile; +using Artemis.Storage.Exceptions; +using Artemis.Storage.Migrations; +using Artemis.Storage.Repositories.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Artemis.Storage.Repositories; + +public class ProfileRepository(Func getContext, List profileMigrators) : IProfileRepository +{ + public void Add(ProfileContainerEntity profileContainerEntity) + { + using ArtemisDbContext dbContext = getContext(); + dbContext.ProfileContainers.Add(profileContainerEntity); + dbContext.SaveChanges(); + } + + public void Remove(ProfileContainerEntity profileContainerEntity) + { + using ArtemisDbContext dbContext = getContext(); + dbContext.ProfileContainers.Remove(profileContainerEntity); + dbContext.SaveChanges(); + } + + public void Save(ProfileContainerEntity profileContainerEntity) + { + using ArtemisDbContext dbContext = getContext(); + dbContext.Update(profileContainerEntity); + dbContext.SaveChanges(); + } + + public void SaveRange(List profileContainerEntities) + { + using ArtemisDbContext dbContext = getContext(); + dbContext.UpdateRange(profileContainerEntities); + dbContext.SaveChanges(); + } + + public void MigrateProfiles() + { + using ArtemisDbContext dbContext = getContext(); + int max = profileMigrators.Max(m => m.Version); + + // Query the ProfileContainerEntity table directly, grabbing the ID, profile, and configuration + List containers = dbContext.Database + .SqlQueryRaw("SELECT Id, Profile, ProfileConfiguration FROM ProfileContainers WHERE json_extract(ProfileConfiguration, '$.Version') < {0}", max) + .ToList(); + + foreach (RawProfileContainer rawProfileContainer in containers) + { + JsonObject? profileConfiguration = JsonNode.Parse(rawProfileContainer.ProfileConfiguration)?.AsObject(); + JsonObject? profile = JsonNode.Parse(rawProfileContainer.Profile)?.AsObject(); + + if (profileConfiguration == null || profile == null) + throw new ArtemisStorageException("Failed to parse profile or profile configuration"); + + MigrateProfile(profileConfiguration, profile); + rawProfileContainer.Profile = profile.ToString(); + rawProfileContainer.ProfileConfiguration = profileConfiguration.ToString(); + + // Write the updated containers back to the database + dbContext.Database.ExecuteSqlRaw( + "UPDATE ProfileContainers SET Profile = {0}, ProfileConfiguration = {1} WHERE Id = {2}", + rawProfileContainer.Profile, + rawProfileContainer.ProfileConfiguration, + rawProfileContainer.Id); + } + } + + public void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson) + { + if (configurationJson == null || profileJson == null) + return; + + configurationJson["Version"] ??= 0; + + foreach (IProfileMigration profileMigrator in profileMigrators.OrderBy(m => m.Version)) + { + if (profileMigrator.Version <= configurationJson["Version"]!.GetValue()) + continue; + + profileMigrator.Migrate(configurationJson, profileJson); + configurationJson["Version"] = profileMigrator.Version; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ReleaseRepository.cs b/src/Artemis.Storage/Repositories/ReleaseRepository.cs index d7e320919..16f5dd7a6 100644 --- a/src/Artemis.Storage/Repositories/ReleaseRepository.cs +++ b/src/Artemis.Storage/Repositories/ReleaseRepository.cs @@ -5,34 +5,24 @@ namespace Artemis.Storage.Repositories; -public class ReleaseRepository : IReleaseRepository +public class ReleaseRepository(Func getContext) : IReleaseRepository { - private readonly ArtemisDbContext _dbContext; - - public ReleaseRepository(ArtemisDbContext dbContext) - { - _dbContext = dbContext; - } - public bool SaveVersionInstallDate(string version) { - ReleaseEntity? release = _dbContext.Releases.FirstOrDefault(r => r.Version == version); + using ArtemisDbContext dbContext = getContext(); + + ReleaseEntity? release = dbContext.Releases.FirstOrDefault(r => r.Version == version); if (release != null) return false; - _dbContext.Releases.Add(new ReleaseEntity {Version = version, InstalledAt = DateTimeOffset.UtcNow}); - _dbContext.SaveChanges(); + dbContext.Releases.Add(new ReleaseEntity {Version = version, InstalledAt = DateTimeOffset.UtcNow}); + dbContext.SaveChanges(); return true; } public ReleaseEntity? GetPreviousInstalledVersion() { - return _dbContext.Releases.OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault(); + using ArtemisDbContext dbContext = getContext(); + return dbContext.Releases.OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault(); } -} - -public interface IReleaseRepository : IRepository -{ - bool SaveVersionInstallDate(string version); - ReleaseEntity? GetPreviousInstalledVersion(); } \ No newline at end of file diff --git a/src/Artemis.UI.Linux/App.axaml.cs b/src/Artemis.UI.Linux/App.axaml.cs index 88f0f8179..dbba1fb2f 100644 --- a/src/Artemis.UI.Linux/App.axaml.cs +++ b/src/Artemis.UI.Linux/App.axaml.cs @@ -1,5 +1,6 @@ using System; using Artemis.Core.Services; +using Artemis.Storage.Legacy; using Artemis.UI.Linux.DryIoc; using Artemis.UI.Linux.Providers.Input; using Avalonia; @@ -20,7 +21,10 @@ public class App : Application public override void Initialize() { _container = ArtemisBootstrapper.Bootstrap(this, c => c.RegisterProviders()); + Program.CreateLogger(_container); + LegacyMigrationService.MigrateToSqlite(_container); + RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; AvaloniaXamlLoader.Load(this); } diff --git a/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj b/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj index c41b62a40..ed8f08fda 100644 --- a/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj +++ b/src/Artemis.UI.Linux/Artemis.UI.Linux.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Artemis.UI.MacOS/App.axaml.cs b/src/Artemis.UI.MacOS/App.axaml.cs index 0d81b318d..800bb523f 100644 --- a/src/Artemis.UI.MacOS/App.axaml.cs +++ b/src/Artemis.UI.MacOS/App.axaml.cs @@ -1,3 +1,4 @@ +using Artemis.Storage.Legacy; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; @@ -14,7 +15,10 @@ public class App : Application public override void Initialize() { _container = ArtemisBootstrapper.Bootstrap(this); + Program.CreateLogger(_container); + LegacyMigrationService.MigrateToSqlite(_container); + RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; AvaloniaXamlLoader.Load(this); } diff --git a/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj b/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj index d762afde1..f322b73d7 100644 --- a/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj +++ b/src/Artemis.UI.MacOS/Artemis.UI.MacOS.csproj @@ -16,6 +16,7 @@ + \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj index bf6734a2a..42faecb23 100644 --- a/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj +++ b/src/Artemis.UI.Shared/Artemis.UI.Shared.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index 41ed8e923..66d5aac1b 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -7,6 +7,7 @@ using System.Threading; using Artemis.Core; using Artemis.Core.Services; +using Artemis.Storage.Legacy; using Artemis.UI.Windows.DryIoc; using Artemis.UI.Windows.Providers.Input; using Avalonia; @@ -34,7 +35,10 @@ public override void Initialize() } _container = ArtemisBootstrapper.Bootstrap(this, c => c.RegisterProviders()); + Program.CreateLogger(_container); + LegacyMigrationService.MigrateToSqlite(_container); + RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; AvaloniaXamlLoader.Load(this); } diff --git a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj index 52a121156..f74f2bd4b 100644 --- a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj +++ b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj @@ -30,6 +30,7 @@ + diff --git a/src/Artemis.UI/Services/Updating/UpdateService.cs b/src/Artemis.UI/Services/Updating/UpdateService.cs index deb6484ea..a6259db37 100644 --- a/src/Artemis.UI/Services/Updating/UpdateService.cs +++ b/src/Artemis.UI/Services/Updating/UpdateService.cs @@ -6,6 +6,7 @@ using Artemis.Core; using Artemis.Core.Services; using Artemis.Storage.Repositories; +using Artemis.Storage.Repositories.Interfaces; using Artemis.UI.Exceptions; using Artemis.UI.Shared.Services.MainWindow; using Artemis.WebClient.Updating; diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs index 4ee9e0cb5..1219f84d6 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs @@ -76,7 +76,7 @@ public async Task UninstallAsync(InstalledEntry installedE // Find the profile if still there ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId); if (profile != null) - _profileService.DeleteProfile(profile); + _profileService.RemoveProfileConfiguration(profile); // Remove the release _workshopService.RemoveInstalledEntry(installedEntry); diff --git a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs index 3da255d12..95b90ccc5 100644 --- a/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs +++ b/src/Artemis.WebClient.Workshop/Services/WorkshopService.cs @@ -171,12 +171,7 @@ public void RemoveInstalledEntry(InstalledEntry installedEntry) public void SaveInstalledEntry(InstalledEntry entry) { entry.Save(); - - // Upsert for plebs - if (entry.Entity.Id == Guid.Empty) - _entryRepository.Add(entry.Entity); - else - _entryRepository.SaveChanges(); + _entryRepository.Save(entry.Entity); } /// diff --git a/src/Artemis.sln b/src/Artemis.sln index 984eb7997..7f7a038f0 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -29,7 +29,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Storage.Migrator", "Artemis.Storage.Migrator\Artemis.Storage.Migrator.csproj", "{D7B0966D-774A-40E4-9455-00C1261ACB6A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Storage.Legacy", "Artemis.Storage.Legacy\Artemis.Storage.Legacy.csproj", "{D7B0966D-774A-40E4-9455-00C1261ACB6A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 3c9d036db..f997dd00e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -25,12 +25,13 @@ + - + From 1cbf6587a1861b8277584b008b29b7c0b11d6a38 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Sun, 10 Mar 2024 21:25:49 +0100 Subject: [PATCH 06/14] CI - Use feature/sqlite branch of plugisn --- .github/workflows/master.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 59204a4bf..be86fa704 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -56,6 +56,7 @@ jobs: uses: actions/checkout@v4 with: repository: Artemis-RGB/Artemis.Plugins + ref: feature/sqlite path: Artemis.Plugins - name: Setup .NET uses: actions/setup-dotnet@v4 From d7c776323c7dbde70fff1df4089065caa988f92a Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Sun, 10 Mar 2024 22:07:04 +0100 Subject: [PATCH 07/14] UI - Try to die a bit more gracefully --- src/Artemis.UI/Screens/Root/RootViewModel.cs | 40 +++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 6b01838a9..f005ce788 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -19,6 +19,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using ReactiveUI; +using Serilog; namespace Artemis.UI.Screens.Root; @@ -28,13 +29,15 @@ public class RootViewModel : RoutableHostScreen, IMainWindowProv private readonly IDebugService _debugService; private readonly DefaultTitleBarViewModel _defaultTitleBarViewModel; private readonly IClassicDesktopStyleApplicationLifetime _lifeTime; + private readonly ILogger _logger; private readonly IRouter _router; private readonly ISettingsService _settingsService; private readonly IUpdateService _updateService; private readonly IWindowService _windowService; private readonly ObservableAsPropertyHelper _titleBarViewModel; - public RootViewModel(IRouter router, + public RootViewModel(ILogger logger, + IRouter router, ICoreService coreService, ISettingsService settingsService, IRegistrationService registrationService, @@ -50,6 +53,7 @@ public RootViewModel(IRouter router, WindowSizeSetting = settingsService.GetSetting("WindowSize"); SidebarViewModel = sidebarViewModel; + _logger = logger; _router = router; _coreService = coreService; _settingsService = settingsService; @@ -81,19 +85,27 @@ public RootViewModel(IRouter router, Task.Run(() => { - // Before doing heavy lifting, initialize the update service which may prompt a restart - // Only initialize with an update check if we're not going to show the UI - if (_updateService.Initialize(!ShouldShowUI())) - return; - - // Workshop service goes first so it has a chance to clean up old workshop entries and introduce new ones - workshopService.Initialize(); - // Core is initialized now that everything is ready to go - coreService.Initialize(); - - registrationService.RegisterBuiltInDataModelDisplays(); - registrationService.RegisterBuiltInDataModelInputs(); - registrationService.RegisterBuiltInPropertyEditors(); + try + { + // Before doing heavy lifting, initialize the update service which may prompt a restart + // Only initialize with an update check if we're not going to show the UI + if (_updateService.Initialize(!ShouldShowUI())) + return; + + // Workshop service goes first so it has a chance to clean up old workshop entries and introduce new ones + workshopService.Initialize(); + // Core is initialized now that everything is ready to go + coreService.Initialize(); + + registrationService.RegisterBuiltInDataModelDisplays(); + registrationService.RegisterBuiltInDataModelInputs(); + registrationService.RegisterBuiltInPropertyEditors(); + } + catch (Exception e) + { + _logger.Fatal(e, "Error during initialization"); + _windowService.ShowExceptionDialog("Fatal error occured during initialization", e); + } }); } From ff0ad99b4ac44637c5f915dd75b963c4b906a5ac Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Sun, 10 Mar 2024 22:31:19 +0100 Subject: [PATCH 08/14] Release repository - Sort by date in memory --- src/Artemis.Storage/Repositories/ReleaseRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Artemis.Storage/Repositories/ReleaseRepository.cs b/src/Artemis.Storage/Repositories/ReleaseRepository.cs index 16f5dd7a6..7dff21928 100644 --- a/src/Artemis.Storage/Repositories/ReleaseRepository.cs +++ b/src/Artemis.Storage/Repositories/ReleaseRepository.cs @@ -23,6 +23,6 @@ public bool SaveVersionInstallDate(string version) public ReleaseEntity? GetPreviousInstalledVersion() { using ArtemisDbContext dbContext = getContext(); - return dbContext.Releases.OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault(); + return dbContext.Releases.AsEnumerable().OrderByDescending(r => r.InstalledAt).Skip(1).FirstOrDefault(); } } \ No newline at end of file From 1196a953362fc90cd766fc0cfede2920d17e8d82 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 11 Mar 2024 20:37:36 +0100 Subject: [PATCH 09/14] Materialize before returning data from GetAll calls --- src/Artemis.Storage/Repositories/DeviceRepository.cs | 2 +- src/Artemis.Storage/Repositories/EntryRepository.cs | 2 +- .../Repositories/Interfaces/IProfileCategoryRepository.cs | 3 +-- src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Artemis.Storage/Repositories/DeviceRepository.cs b/src/Artemis.Storage/Repositories/DeviceRepository.cs index 2eb7098c2..af3935161 100644 --- a/src/Artemis.Storage/Repositories/DeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/DeviceRepository.cs @@ -31,7 +31,7 @@ public void Remove(DeviceEntity deviceEntity) public IEnumerable GetAll() { using ArtemisDbContext dbContext = getContext(); - return dbContext.Devices; + return dbContext.Devices.AsEnumerable(); } public void Save(DeviceEntity deviceEntity) diff --git a/src/Artemis.Storage/Repositories/EntryRepository.cs b/src/Artemis.Storage/Repositories/EntryRepository.cs index a901e637d..52bb8df7b 100644 --- a/src/Artemis.Storage/Repositories/EntryRepository.cs +++ b/src/Artemis.Storage/Repositories/EntryRepository.cs @@ -37,7 +37,7 @@ public void Remove(EntryEntity entryEntity) public IEnumerable GetAll() { using ArtemisDbContext dbContext = getContext(); - return dbContext.Entries; + return dbContext.Entries.AsEnumerable(); } public void Save(EntryEntity entryEntity) diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs index 533e8990e..f2cb69f17 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using Artemis.Storage.Entities.Profile; namespace Artemis.Storage.Repositories.Interfaces; @@ -9,7 +8,7 @@ public interface IProfileCategoryRepository : IRepository { void Add(ProfileCategoryEntity profileCategoryEntity); void Remove(ProfileCategoryEntity profileCategoryEntity); - List GetAll(); + IEnumerable GetAll(); ProfileCategoryEntity? Get(Guid id); bool IsUnique(string name, Guid? id); void Save(ProfileCategoryEntity profileCategoryEntity); diff --git a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs index 90e53c731..e1e58559e 100644 --- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs @@ -25,7 +25,7 @@ public void Remove(ProfileCategoryEntity profileCategoryEntity) dbContext.SaveChanges(); } - public List GetAll() + public IEnumerable GetAll() { if (!_migratedProfiles) { @@ -34,7 +34,7 @@ public List GetAll() } using ArtemisDbContext dbContext = getContext(); - return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).ToList(); + return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).AsEnumerable(); } public ProfileCategoryEntity? Get(Guid id) From f66df43cc23f51ea45a0dab537398f4e99be9215 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 12 Mar 2024 08:43:20 +0100 Subject: [PATCH 10/14] Materialize before returning data from GetAll calls, tested this time --- src/Artemis.Storage/Repositories/DeviceRepository.cs | 4 ++-- src/Artemis.Storage/Repositories/EntryRepository.cs | 4 ++-- .../Repositories/Interfaces/IDeviceRepository.cs | 2 +- .../Repositories/Interfaces/IEntryRepository.cs | 2 +- .../Repositories/Interfaces/IProfileCategoryRepository.cs | 2 +- src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Artemis.Storage/Repositories/DeviceRepository.cs b/src/Artemis.Storage/Repositories/DeviceRepository.cs index af3935161..9e7353a3c 100644 --- a/src/Artemis.Storage/Repositories/DeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/DeviceRepository.cs @@ -28,10 +28,10 @@ public void Remove(DeviceEntity deviceEntity) return dbContext.Devices.FirstOrDefault(d => d.Id == id); } - public IEnumerable GetAll() + public List GetAll() { using ArtemisDbContext dbContext = getContext(); - return dbContext.Devices.AsEnumerable(); + return dbContext.Devices.ToList(); } public void Save(DeviceEntity deviceEntity) diff --git a/src/Artemis.Storage/Repositories/EntryRepository.cs b/src/Artemis.Storage/Repositories/EntryRepository.cs index 52bb8df7b..43a155e66 100644 --- a/src/Artemis.Storage/Repositories/EntryRepository.cs +++ b/src/Artemis.Storage/Repositories/EntryRepository.cs @@ -34,10 +34,10 @@ public void Remove(EntryEntity entryEntity) return dbContext.Entries.FirstOrDefault(s => s.EntryId == entryId); } - public IEnumerable GetAll() + public List GetAll() { using ArtemisDbContext dbContext = getContext(); - return dbContext.Entries.AsEnumerable(); + return dbContext.Entries.ToList(); } public void Save(EntryEntity entryEntity) diff --git a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs index f2a77e3d2..52e5eec15 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs @@ -8,7 +8,7 @@ public interface IDeviceRepository : IRepository void Add(DeviceEntity deviceEntity); void Remove(DeviceEntity deviceEntity); DeviceEntity? Get(string id); - IEnumerable GetAll(); + List GetAll(); void Save(DeviceEntity deviceEntity); void SaveRange(IEnumerable deviceEntities); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs index 46722068e..586542692 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs @@ -10,6 +10,6 @@ public interface IEntryRepository : IRepository void Remove(EntryEntity entryEntity); EntryEntity? Get(Guid id); EntryEntity? GetByEntryId(long entryId); - IEnumerable GetAll(); + List GetAll(); void Save(EntryEntity entryEntity); } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs index f2cb69f17..96ca0d7d2 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileCategoryRepository.cs @@ -8,7 +8,7 @@ public interface IProfileCategoryRepository : IRepository { void Add(ProfileCategoryEntity profileCategoryEntity); void Remove(ProfileCategoryEntity profileCategoryEntity); - IEnumerable GetAll(); + List GetAll(); ProfileCategoryEntity? Get(Guid id); bool IsUnique(string name, Guid? id); void Save(ProfileCategoryEntity profileCategoryEntity); diff --git a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs index e1e58559e..90e53c731 100644 --- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs @@ -25,7 +25,7 @@ public void Remove(ProfileCategoryEntity profileCategoryEntity) dbContext.SaveChanges(); } - public IEnumerable GetAll() + public List GetAll() { if (!_migratedProfiles) { @@ -34,7 +34,7 @@ public IEnumerable GetAll() } using ArtemisDbContext dbContext = getContext(); - return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).AsEnumerable(); + return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).ToList(); } public ProfileCategoryEntity? Get(Guid id) From d7a0c2ac4a8fd2985fb4ee3ebdec636d3a0fa389 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 12 Mar 2024 21:26:47 +0100 Subject: [PATCH 11/14] Core - Delay start watching plugins for hot reload after initializing UI - Simplify category management logic UI - Avoid crash during profile icon load Storage - Fix entry metadata retrieval --- .../Models/Profile/ProfileCategory.cs | 19 ++---- src/Artemis.Core/Plugins/Plugin.cs | 8 ++- src/Artemis.Core/Services/CoreService.cs | 1 + .../Interfaces/IPluginManagementService.cs | 9 ++- .../Services/PluginManagementService.cs | 66 +++++++++---------- .../Storage/Interfaces/IProfileService.cs | 4 +- .../Services/Storage/ProfileService.cs | 8 +-- .../Entities/Workshop/EntryEntity.cs | 8 ++- src/Artemis.Storage/ArtemisDbContext.cs | 3 +- .../Entities/Workshop/EntryEntity.cs | 3 +- .../Repositories/ProfileRepository.cs | 5 +- .../ProfileConfigurationIcon.axaml.cs | 15 +++-- .../ProfileConfigurationDropHandler.cs | 4 +- .../Sidebar/SidebarCategoryViewModel.cs | 4 +- .../Models/InstalledEntry.cs | 16 +++-- 15 files changed, 91 insertions(+), 82 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/ProfileCategory.cs b/src/Artemis.Core/Models/Profile/ProfileCategory.cs index cdab02e8a..1a5c6f269 100644 --- a/src/Artemis.Core/Models/Profile/ProfileCategory.cs +++ b/src/Artemis.Core/Models/Profile/ProfileCategory.cs @@ -98,26 +98,15 @@ public bool IsSuspended /// /// Adds a profile configuration to this category /// - public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex) + public void AddProfileConfiguration(ProfileConfiguration configuration, ProfileConfiguration? target) { - List targetList = ProfileConfigurations.ToList(); - - // TODO: Look into this, it doesn't seem to make sense - // Removing the original will shift every item in the list forwards, keep that in mind with the target index - if (configuration.Category == this && targetIndex != null && targetIndex.Value > targetList.IndexOf(configuration)) - targetIndex -= 1; - + List targetList = ProfileConfigurations.Where(c => c!= configuration).ToList(); configuration.Category.RemoveProfileConfiguration(configuration); - if (targetIndex != null) - { - targetIndex = Math.Clamp(targetIndex.Value, 0, targetList.Count); - targetList.Insert(targetIndex.Value, configuration); - } + if (target != null) + targetList.Insert(targetList.IndexOf(target), configuration); else - { targetList.Add(configuration); - } configuration.Category = this; ProfileConfigurations = new ReadOnlyCollection(targetList); diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 5eacf18f0..6f2d2b4d4 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -357,10 +357,10 @@ internal bool HasEnabledFeatures() return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled); } - internal void AutoEnableIfNew() + internal bool AutoEnableIfNew() { if (_loadedFromStorage) - return; + return false; // Enabled is preset to true if the plugin meets the following criteria // - Requires no admin rights @@ -371,11 +371,13 @@ internal void AutoEnableIfNew() Info.ArePrerequisitesMet(); if (!Entity.IsEnabled) - return; + return false; // Also auto-enable any non-device provider feature foreach (PluginFeatureInfo pluginFeatureInfo in Features) pluginFeatureInfo.Entity.IsEnabled = !pluginFeatureInfo.FeatureType.IsAssignableTo(typeof(DeviceProvider)); + + return true; } /// diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index b27a8601b..da0f62fd7 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -71,6 +71,7 @@ public void Initialize() // Initialize the services _pluginManagementService.CopyBuiltInPlugins(); _pluginManagementService.LoadPlugins(IsElevated); + _pluginManagementService.StartHotReload(); _renderService.Initialize(); OnInitialized(); diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index 932e3f507..fbd97344d 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -16,7 +16,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable /// Gets a list containing additional directories in which plugins are located, used while loading plugins. /// List AdditionalPluginDirectories { get; } - + /// /// Indicates whether or not plugins are currently being loaded /// @@ -33,6 +33,11 @@ public interface IPluginManagementService : IArtemisService, IDisposable /// void LoadPlugins(bool isElevated); + /// + /// Starts monitoring plugin directories for changes and reloads plugins when changes are detected + /// + void StartHotReload(); + /// /// Unloads all installed plugins. /// @@ -145,7 +150,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable /// /// DeviceProvider GetDeviceProviderByDevice(IRGBDevice device); - + /// /// Occurs when built-in plugins are being loaded /// diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 62bc658ac..0334417f6 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -41,37 +41,6 @@ public PluginManagementService(IContainer container, ILogger logger, IPluginRepo _pluginRepository = pluginRepository; _deviceRepository = deviceRepository; _plugins = new List(); - - StartHotReload(); - } - - private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory) - { - ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json"); - DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory)); - bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock")); - - // Remove the old directory if it exists - if (Directory.Exists(pluginDirectory.FullName)) - pluginDirectory.Delete(true); - - // Extract everything in the same archive directory to the unique plugin directory - Utilities.CreateAccessibleDirectory(pluginDirectory.FullName); - string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, ""); - foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries) - { - if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/")) - { - string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length)); - // Create folders - Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!); - // Extract files - zipArchiveEntry.ExtractToFile(target); - } - } - - if (createLockFile) - File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close(); } public List AdditionalPluginDirectories { get; } = new(); @@ -155,6 +124,35 @@ private void ExtractBuiltInPlugin(FileInfo zipFile, DirectoryInfo pluginDirector } } + private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory) + { + ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json"); + DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory)); + bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock")); + + // Remove the old directory if it exists + if (Directory.Exists(pluginDirectory.FullName)) + pluginDirectory.Delete(true); + + // Extract everything in the same archive directory to the unique plugin directory + Utilities.CreateAccessibleDirectory(pluginDirectory.FullName); + string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, ""); + foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries) + { + if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/")) + { + string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length)); + // Create folders + Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!); + // Extract files + zipArchiveEntry.ExtractToFile(target); + } + } + + if (createLockFile) + File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close(); + } + #endregion public List GetAllPlugins() @@ -444,7 +442,9 @@ public void UnloadPlugins() _logger.Warning("Plugin {plugin} contains no features", plugin); // It is appropriate to call this now that we have the features of this plugin - plugin.AutoEnableIfNew(); + bool autoEnabled = plugin.AutoEnableIfNew(); + if (autoEnabled) + _pluginRepository.SavePlugin(entity); List bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(PluginBootstrapper).IsAssignableFrom(t)).ToList(); if (bootstrappers.Count > 1) @@ -894,7 +894,7 @@ protected virtual void OnPluginFeatureEnableFailed(PluginFeatureEventArgs e) #region Hot Reload - private void StartHotReload() + public void StartHotReload() { // Watch for changes in the plugin directory, "plugin.json". // If this file is changed, reload the plugin. diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index c8a5f8435..2a55664a9 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -115,9 +115,9 @@ public interface IProfileService : IArtemisService /// any changes are made to it. /// /// Text to add after the name of the profile (separated by a dash). - /// The index at which to import the profile into the category. + /// The profile before which to import the profile into the category. /// The resulting profile configuration. - Task ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported", int targetIndex = 0); + Task ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported", ProfileConfiguration? target = null); /// /// Imports the provided ZIP archive stream into the provided profile configuration diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 6b9889e19..913c38e0e 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -262,7 +262,7 @@ public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, { ProfileConfiguration configuration = new(category, name, icon); - category.AddProfileConfiguration(configuration, 0); + category.AddProfileConfiguration(configuration, category.ProfileConfigurations.FirstOrDefault()); SaveProfileCategory(category); return configuration; } @@ -354,7 +354,7 @@ public async Task ExportProfile(ProfileConfiguration profileConfiguratio } /// - public async Task ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix, int targetIndex = 0) + public async Task ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix, ProfileConfiguration? target) { using ZipArchive archive = new(archiveStream, ZipArchiveMode.Read, true); @@ -424,7 +424,7 @@ public async Task ImportProfile(Stream archiveStream, Prof profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}"; profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id; - category.AddProfileConfiguration(profileConfiguration, targetIndex); + category.AddProfileConfiguration(profileConfiguration, target); List modules = _pluginManagementService.GetFeaturesOfType(); profileConfiguration.LoadModules(modules); @@ -436,7 +436,7 @@ public async Task ImportProfile(Stream archiveStream, Prof /// public async Task OverwriteProfile(MemoryStream archiveStream, ProfileConfiguration profileConfiguration) { - ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration.Order + 1); + ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration); RemoveProfileConfiguration(profileConfiguration); SaveProfileCategory(imported.Category); diff --git a/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs index ec0d6c091..aaeecd3bd 100644 --- a/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs @@ -1,4 +1,6 @@ -namespace Artemis.Storage.Legacy.Entities.Workshop; +using System.Text.Json.Nodes; + +namespace Artemis.Storage.Legacy.Entities.Workshop; internal class EntryEntity { @@ -14,7 +16,7 @@ internal class EntryEntity public string ReleaseVersion { get; set; } = string.Empty; public DateTimeOffset InstalledAt { get; set; } - public Dictionary? Metadata { get; set; } + public Dictionary? Metadata { get; set; } public Storage.Entities.Workshop.EntryEntity Migrate() { @@ -29,7 +31,7 @@ public Storage.Entities.Workshop.EntryEntity Migrate() ReleaseId = ReleaseId, ReleaseVersion = ReleaseVersion, InstalledAt = InstalledAt, - Metadata = Metadata ?? new Dictionary() + Metadata = Metadata ?? new Dictionary() }; } } \ No newline at end of file diff --git a/src/Artemis.Storage/ArtemisDbContext.cs b/src/Artemis.Storage/ArtemisDbContext.cs index 2a77604fa..32acba6a6 100644 --- a/src/Artemis.Storage/ArtemisDbContext.cs +++ b/src/Artemis.Storage/ArtemisDbContext.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; +using System.Text.Json.Nodes; using Artemis.Storage.Entities.General; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Profile; @@ -41,7 +42,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Property(e => e.Metadata) .HasConversion( v => JsonSerializer.Serialize(v, JsonSerializerOptions), - v => JsonSerializer.Deserialize>(v, JsonSerializerOptions) ?? new Dictionary()); + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions) ?? new Dictionary()); modelBuilder.Entity() .Property(e => e.ProfileConfiguration) diff --git a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs index 37cdd4cfa..ad11d6188 100644 --- a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json.Nodes; using Microsoft.EntityFrameworkCore; namespace Artemis.Storage.Entities.Workshop; @@ -19,5 +20,5 @@ public class EntryEntity public string ReleaseVersion { get; set; } = string.Empty; public DateTimeOffset InstalledAt { get; set; } - public Dictionary? Metadata { get; set; } + public Dictionary? Metadata { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs index 78c9ca6a0..65e7dab78 100644 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs @@ -8,10 +8,11 @@ using Artemis.Storage.Migrations; using Artemis.Storage.Repositories.Interfaces; using Microsoft.EntityFrameworkCore; +using Serilog; namespace Artemis.Storage.Repositories; -public class ProfileRepository(Func getContext, List profileMigrators) : IProfileRepository +public class ProfileRepository(ILogger logger, Func getContext, List profileMigrators) : IProfileRepository { public void Add(ProfileContainerEntity profileContainerEntity) { @@ -83,6 +84,8 @@ public void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJso { if (profileMigrator.Version <= configurationJson["Version"]!.GetValue()) continue; + + logger.Information("Migrating profile from version {OldVersion} to {NewVersion}", configurationJson["Version"], profileMigrator.Version); profileMigrator.Migrate(configurationJson, profileJson); configurationJson["Version"] = profileMigrator.Version; diff --git a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs index 2ee9934d6..6b7fd2a02 100644 --- a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs @@ -47,7 +47,7 @@ private void Update() : new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; } else if (ConfigurationIcon.IconBytes != null) - Dispatcher.UIThread.Post(() => LoadFromBitmap(ConfigurationIcon, new MemoryStream(ConfigurationIcon.IconBytes)), DispatcherPriority.ApplicationIdle); + Dispatcher.UIThread.Post(LoadFromBitmap, DispatcherPriority.ApplicationIdle); else Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; } @@ -57,14 +57,17 @@ private void Update() } } - private void LoadFromBitmap(Core.ProfileConfigurationIcon configurationIcon, Stream stream) + private void LoadFromBitmap() { try { - _stream = stream; - if (!configurationIcon.Fill) + if (ConfigurationIcon?.IconBytes == null) + return; + + _stream = new MemoryStream(ConfigurationIcon.IconBytes); + if (!ConfigurationIcon.Fill) { - Content = new Image {Source = new Bitmap(stream)}; + Content = new Image {Source = new Bitmap(_stream)}; return; } @@ -73,7 +76,7 @@ private void LoadFromBitmap(Core.ProfileConfigurationIcon configurationIcon, Str Background = TextElement.GetForeground(this), VerticalAlignment = VerticalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch, - OpacityMask = new ImageBrush(new Bitmap(stream)) + OpacityMask = new ImageBrush(new Bitmap(_stream)) }; } catch (Exception) diff --git a/src/Artemis.UI/Screens/Sidebar/Behaviors/ProfileConfigurationDropHandler.cs b/src/Artemis.UI/Screens/Sidebar/Behaviors/ProfileConfigurationDropHandler.cs index 8e4d4d108..b07a43196 100644 --- a/src/Artemis.UI/Screens/Sidebar/Behaviors/ProfileConfigurationDropHandler.cs +++ b/src/Artemis.UI/Screens/Sidebar/Behaviors/ProfileConfigurationDropHandler.cs @@ -68,8 +68,8 @@ private bool Validate(ListBox listBox, DragEventArgs e, object? sourceContext, o { int index = vm.ProfileConfigurations.IndexOf(targetItem); if (!before) - index++; - vm.AddProfileConfiguration(sourceItem.ProfileConfiguration, index); + targetItem = index < vm.ProfileConfigurations.Count - 1 ? vm.ProfileConfigurations[index + 1] : null; + vm.AddProfileConfiguration(sourceItem.ProfileConfiguration, targetItem?.ProfileConfiguration); } else { diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs index 3892fee70..fe6cf2d9e 100644 --- a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs @@ -103,10 +103,10 @@ public SidebarCategoryViewModel(ProfileCategory profileCategory, IProfileService public bool IsCollapsed => _isCollapsed?.Value ?? false; public bool IsSuspended => _isSuspended?.Value ?? false; - public void AddProfileConfiguration(ProfileConfiguration profileConfiguration, int? index) + public void AddProfileConfiguration(ProfileConfiguration profileConfiguration, ProfileConfiguration? target) { ProfileCategory oldCategory = profileConfiguration.Category; - ProfileCategory.AddProfileConfiguration(profileConfiguration, index); + ProfileCategory.AddProfileConfiguration(profileConfiguration, target); _profileService.SaveProfileCategory(ProfileCategory); // If the profile moved to a new category, also save the old category diff --git a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs index 90aa4108e..ce90444b0 100644 --- a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs +++ b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs @@ -1,4 +1,6 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.Json; +using System.Text.Json.Nodes; using Artemis.Core; using Artemis.Storage.Entities.Workshop; @@ -6,7 +8,7 @@ namespace Artemis.WebClient.Workshop.Models; public class InstalledEntry { - private Dictionary _metadata = new(); + private Dictionary _metadata = new(); internal InstalledEntry(EntryEntity entity) { @@ -52,7 +54,7 @@ internal void Load() ReleaseVersion = Entity.ReleaseVersion; InstalledAt = Entity.InstalledAt; - _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); + _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); } internal void Save() @@ -67,7 +69,7 @@ internal void Save() Entity.ReleaseVersion = ReleaseVersion; Entity.InstalledAt = InstalledAt; - Entity.Metadata = new Dictionary(_metadata); + Entity.Metadata = new Dictionary(_metadata); } /// @@ -80,14 +82,14 @@ internal void Save() /// if the metadata contains an element with the specified key; otherwise, . public bool TryGetMetadata(string key, [NotNullWhen(true)] out T? value) { - if (!_metadata.TryGetValue(key, out object? objectValue) || objectValue is not T result) + if (!_metadata.TryGetValue(key, out JsonNode? element)) { value = default; return false; } - value = result; - return true; + value = element.GetValue(); + return value != null; } /// @@ -97,7 +99,7 @@ public bool TryGetMetadata(string key, [NotNullWhen(true)] out T? value) /// The value to set. public void SetMetadata(string key, object value) { - _metadata[key] = value; + _metadata[key] = JsonSerializer.SerializeToNode(value) ?? throw new InvalidOperationException(); } /// From da7a3e484dba98844a337f20fdcb6380f5331bc0 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Tue, 12 Mar 2024 22:18:06 +0100 Subject: [PATCH 12/14] Legacy Storage - Convert objects to JSON nodes during migration --- .../Entities/Workshop/EntryEntity.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs index aaeecd3bd..24c7a2efb 100644 --- a/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Nodes; +using System.Text.Json; +using System.Text.Json.Nodes; namespace Artemis.Storage.Legacy.Entities.Workshop; @@ -16,7 +17,7 @@ internal class EntryEntity public string ReleaseVersion { get; set; } = string.Empty; public DateTimeOffset InstalledAt { get; set; } - public Dictionary? Metadata { get; set; } + public Dictionary? Metadata { get; set; } public Storage.Entities.Workshop.EntryEntity Migrate() { @@ -31,7 +32,7 @@ public Storage.Entities.Workshop.EntryEntity Migrate() ReleaseId = ReleaseId, ReleaseVersion = ReleaseVersion, InstalledAt = InstalledAt, - Metadata = Metadata ?? new Dictionary() + Metadata = Metadata?.ToDictionary(kvp => kvp.Key, kvp => JsonSerializer.SerializeToNode(kvp.Value) ?? new JsonObject()) }; } } \ No newline at end of file From afc22568bae9b37b6d56e508bc05dc6ad5f60dfe Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Wed, 13 Mar 2024 20:16:44 +0100 Subject: [PATCH 13/14] Don't crash on profile migration failures --- .../DryIoc/ContainerExtensions.cs | 4 +- .../Repositories/ProfileCategoryRepository.cs | 6 +++ .../Repositories/ProfileRepository.cs | 46 +++++++++++-------- src/Artemis.Storage/StorageManager.cs | 1 + 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/Artemis.Core/DryIoc/ContainerExtensions.cs b/src/Artemis.Core/DryIoc/ContainerExtensions.cs index 62712427f..eaa2282a8 100644 --- a/src/Artemis.Core/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.Core/DryIoc/ContainerExtensions.cs @@ -22,8 +22,8 @@ public static class ContainerExtensions /// The builder building the current container public static void RegisterCore(this IContainer container) { - Assembly[] coreAssembly = {typeof(IArtemisService).Assembly}; - Assembly[] storageAssembly = {typeof(IRepository).Assembly}; + Assembly[] coreAssembly = [typeof(IArtemisService).Assembly]; + Assembly[] storageAssembly = [typeof(IRepository).Assembly]; // Bind all services as singletons container.RegisterMany(coreAssembly, type => type.IsAssignableTo(), Reuse.Singleton); diff --git a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs index 90e53c731..5ad5ae4d7 100644 --- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs @@ -39,6 +39,12 @@ public List GetAll() public ProfileCategoryEntity? Get(Guid id) { + if (!_migratedProfiles) + { + profileRepository.MigrateProfiles(); + _migratedProfiles = true; + } + using ArtemisDbContext dbContext = getContext(); return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).FirstOrDefault(c => c.Id == id); } diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs index 65e7dab78..84e72cb2b 100644 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs @@ -54,22 +54,32 @@ public void MigrateProfiles() foreach (RawProfileContainer rawProfileContainer in containers) { - JsonObject? profileConfiguration = JsonNode.Parse(rawProfileContainer.ProfileConfiguration)?.AsObject(); - JsonObject? profile = JsonNode.Parse(rawProfileContainer.Profile)?.AsObject(); - - if (profileConfiguration == null || profile == null) - throw new ArtemisStorageException("Failed to parse profile or profile configuration"); - - MigrateProfile(profileConfiguration, profile); - rawProfileContainer.Profile = profile.ToString(); - rawProfileContainer.ProfileConfiguration = profileConfiguration.ToString(); - - // Write the updated containers back to the database - dbContext.Database.ExecuteSqlRaw( - "UPDATE ProfileContainers SET Profile = {0}, ProfileConfiguration = {1} WHERE Id = {2}", - rawProfileContainer.Profile, - rawProfileContainer.ProfileConfiguration, - rawProfileContainer.Id); + try + { + JsonObject? profileConfiguration = JsonNode.Parse(rawProfileContainer.ProfileConfiguration)?.AsObject(); + JsonObject? profile = JsonNode.Parse(rawProfileContainer.Profile)?.AsObject(); + + if (profileConfiguration == null || profile == null) + { + logger.Error("Failed to parse profile or profile configuration of profile container {Id}", rawProfileContainer.Id); + continue; + } + + MigrateProfile(profileConfiguration, profile); + rawProfileContainer.Profile = profile.ToString(); + rawProfileContainer.ProfileConfiguration = profileConfiguration.ToString(); + + // Write the updated containers back to the database + dbContext.Database.ExecuteSqlRaw( + "UPDATE ProfileContainers SET Profile = {0}, ProfileConfiguration = {1} WHERE Id = {2}", + rawProfileContainer.Profile, + rawProfileContainer.ProfileConfiguration, + rawProfileContainer.Id); + } + catch (Exception e) + { + logger.Error(e, "Failed to migrate profile container {Id}", rawProfileContainer.Id); + } } } @@ -84,8 +94,8 @@ public void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJso { if (profileMigrator.Version <= configurationJson["Version"]!.GetValue()) continue; - - logger.Information("Migrating profile from version {OldVersion} to {NewVersion}", configurationJson["Version"], profileMigrator.Version); + + logger.Information("Migrating profile '{Name}' from version {OldVersion} to {NewVersion}", configurationJson["Name"], configurationJson["Version"], profileMigrator.Version); profileMigrator.Migrate(configurationJson, profileJson); configurationJson["Version"] = profileMigrator.Version; diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs index d7c255363..c87a86be1 100644 --- a/src/Artemis.Storage/StorageManager.cs +++ b/src/Artemis.Storage/StorageManager.cs @@ -49,6 +49,7 @@ public static ArtemisDbContext CreateDbContext(string dataFolder) return dbContext; dbContext.Database.Migrate(); + dbContext.Database.ExecuteSqlRaw("PRAGMA optimize"); _ranMigrations = true; return dbContext; From b016b0475d0e09e363e9c50a0a4c3c7729784875 Mon Sep 17 00:00:00 2001 From: RobertBeekman Date: Wed, 13 Mar 2024 20:17:44 +0100 Subject: [PATCH 14/14] Use master builds of plugins once again --- .github/workflows/master.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index be86fa704..59204a4bf 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -56,7 +56,6 @@ jobs: uses: actions/checkout@v4 with: repository: Artemis-RGB/Artemis.Plugins - ref: feature/sqlite path: Artemis.Plugins - name: Setup .NET uses: actions/setup-dotnet@v4