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 c61aa021c..dfa7791c5 100644
--- a/src/Artemis.Core/Constants.cs
+++ b/src/Artemis.Core/Constants.cs
@@ -6,6 +6,7 @@ using System.Linq;
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(){PluginGuid = 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 8aa35b65f..eaa2282a8 100644
--- a/src/Artemis.Core/DryIoc/ContainerExtensions.cs
+++ b/src/Artemis.Core/DryIoc/ContainerExtensions.cs
@@ -22,20 +22,18 @@ 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);
container.RegisterMany(coreAssembly, type => type.IsAssignableTo(), Reuse.Singleton, setup: Setup.With(condition: HasAccessToProtectedService));
// Bind storage
- container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton);
- container.Register(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);
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/Profile/ProfileCategory.cs b/src/Artemis.Core/Models/Profile/ProfileCategory.cs
index 4dc3f73e6..1a5c6f269 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 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
_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 class ProfileCategory : CorePropertyChanged, IStorageModel
///
/// 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
@@ -96,29 +98,21 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
///
/// Adds a profile configuration to this category
///
- public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex)
+ public void AddProfileConfiguration(ProfileConfiguration configuration, ProfileConfiguration? target)
{
- // 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))
- targetIndex -= 1;
-
+ List targetList = ProfileConfigurations.Where(c => c!= configuration).ToList();
configuration.Category.RemoveProfileConfiguration(configuration);
-
- if (targetIndex != null)
- {
- targetIndex = Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count);
- _profileConfigurations.Insert(targetIndex.Value, configuration);
- }
+
+ if (target != null)
+ targetList.Insert(targetList.IndexOf(target), 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 +150,10 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
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 +167,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
IsSuspended = Entity.IsSuspended;
Order = Entity.Order;
- _profileConfigurations.Clear();
- foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations)
- _profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration));
+ ProfileConfigurations = new ReadOnlyCollection(Entity.ProfileConfigurations.Select(pc => new ProfileConfiguration(this, pc)).ToList());
}
///
@@ -186,13 +177,10 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
Entity.IsCollapsed = IsCollapsed;
Entity.IsSuspended = IsSuspended;
Entity.Order = Order;
-
+
Entity.ProfileConfigurations.Clear();
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
- {
- profileConfiguration.Save();
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 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
_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 class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
///
/// 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 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
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 class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
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 class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
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..c935836db 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 class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
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 class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
/// 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 class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
}
///
- /// 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 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
///
public void Load()
{
- IconType = (ProfileConfigurationIconType) _entity.IconType;
- Fill = _entity.IconFill;
- if (IconType != ProfileConfigurationIconType.MaterialIcon)
- return;
+ IconType = (ProfileConfigurationIconType) _entity.ProfileConfiguration.IconType;
+ Fill = _entity.ProfileConfiguration.IconFill;
- IconName = _entity.MaterialIcon;
+ if (IconType == ProfileConfigurationIconType.MaterialIcon)
+ IconName = _entity.ProfileConfiguration.MaterialIcon;
+ else
+ IconBytes = _entity.Icon;
+
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/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 @@ public class ArtemisDeviceInputIdentifier
///
/// 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/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs
index ff90181bc..6f2d2b4d4 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();
@@ -309,13 +309,7 @@ public class Plugin : CorePropertyChanged, IDisposable
{
FeatureRemoved?.Invoke(this, e);
}
-
- internal void ApplyToEntity()
- {
- Entity.Id = Guid;
- Entity.IsEnabled = IsEnabled;
- }
-
+
internal void AddFeature(PluginFeatureInfo featureInfo)
{
if (featureInfo.Plugin != this)
@@ -363,10 +357,10 @@ public class Plugin : CorePropertyChanged, IDisposable
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
@@ -377,11 +371,13 @@ public class Plugin : CorePropertyChanged, IDisposable
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/Plugins/Settings/IPluginSetting.cs b/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs
index 57a454420..e27282063 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,19 +8,18 @@ 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
///
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/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 @@ public class PluginSettings
/// 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 class PluginSettings
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/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index 89a951149..da0f62fd7 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,
@@ -72,6 +71,7 @@ internal class CoreService : ICoreService
// Initialize the services
_pluginManagementService.CopyBuiltInPlugins();
_pluginManagementService.LoadPlugins(IsElevated);
+ _pluginManagementService.StartHotReload();
_renderService.Initialize();
OnInitialized();
diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs
index afc949bba..da81a82a6 100644
--- a/src/Artemis.Core/Services/DeviceService.cs
+++ b/src/Artemis.Core/Services/DeviceService.cs
@@ -236,7 +236,7 @@ internal class DeviceService : IDeviceService
{
foreach (ArtemisDevice artemisDevice in _devices)
artemisDevice.Save();
- _deviceRepository.Save(_devices.Select(d => d.DeviceEntity));
+ _deviceRepository.SaveRange(_devices.Select(d => d.DeviceEntity));
UpdateLeds();
}
@@ -254,6 +254,7 @@ internal class DeviceService : IDeviceService
{
_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);
}
LoadDeviceLayout(device);
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 class InputProviderIdentifierEventArgs : EventArgs
///
/// 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 @@ public abstract class InputProvider : IDisposable
///
/// 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..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.
///
@@ -146,18 +151,6 @@ 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..0334417f6 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -30,52 +30,17 @@ 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();
- }
-
- 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();
@@ -159,6 +124,35 @@ internal class PluginManagementService : IPluginManagementService
}
}
+ 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()
@@ -376,7 +370,15 @@ internal class PluginManagementService : IPluginManagementService
}
// Load the entity and fall back on creating a new one
- Plugin plugin = new(pluginInfo, directory, _pluginRepository.GetPluginByGuid(pluginInfo.Guid));
+ PluginEntity? entity = _pluginRepository.GetPluginByPluginGuid(pluginInfo.Guid);
+ bool loadedFromStorage = entity != null;
+ if (entity == null)
+ {
+ entity = new PluginEntity {PluginGuid = pluginInfo.Guid};
+ _pluginRepository.SavePlugin(entity);
+ }
+
+ Plugin plugin = new(pluginInfo, directory, entity, loadedFromStorage);
OnPluginLoading(new PluginEventArgs(plugin));
// Locate the main assembly entry
@@ -440,7 +442,9 @@ internal class PluginManagementService : IPluginManagementService
_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)
@@ -801,58 +805,6 @@ internal class PluginManagementService : IPluginManagementService
#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)
@@ -942,7 +894,7 @@ internal class PluginManagementService : IPluginManagementService
#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/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 @@ internal class RenderService : IRenderService, IRenderer, IDisposable
_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/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 @@ internal class ScriptingService : IScriptingService
// 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 @@ internal class ScriptingService : IScriptingService
{
_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/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 2d2dfae7c..2a55664a9 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.
///
@@ -59,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.
@@ -101,16 +90,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.
///
@@ -136,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 5cafbd7db..913c38e0e 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;
@@ -21,11 +22,10 @@ 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();
- private readonly List _profileCategories;
- private readonly IProfileRepository _profileRepository;
private readonly List _profileMigrators;
private readonly List _renderExceptions = new();
private readonly List _updateExceptions = new();
@@ -35,19 +35,20 @@ internal class ProfileService : IProfileService
public ProfileService(ILogger logger,
IProfileCategoryRepository profileCategoryRepository,
+ IProfileRepository profileRepository,
IPluginManagementService pluginManagementService,
IInputService inputService,
IDeviceService deviceService,
- IProfileRepository profileRepository,
List profileMigrators)
{
_logger = logger;
_profileCategoryRepository = profileCategoryRepository;
+ _profileRepository = profileRepository;
_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(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order).ToList());
_deviceService.LedsChanged += DeviceServiceOnLedsChanged;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
@@ -55,7 +56,7 @@ internal class ProfileService : IProfileService
inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
- if (!_profileCategories.Any())
+ if (!ProfileCategories.Any())
CreateDefaultProfileCategories();
UpdateModules();
}
@@ -77,55 +78,52 @@ internal class ProfileService : IProfileService
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];
+
+ // Process hotkeys that where pressed since this profile last updated
+ ProcessPendingKeyEvents(profileConfiguration);
+
+ bool shouldBeActive = profileConfiguration.ShouldBeActive(false);
+ if (shouldBeActive)
{
- ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
+ profileConfiguration.Update();
+ shouldBeActive = profileConfiguration.ActivationConditionMet;
+ }
- // Process hotkeys that where pressed since this profile last updated
- ProcessPendingKeyEvents(profileConfiguration);
-
- bool shouldBeActive = profileConfiguration.ShouldBeActive(false);
- if (shouldBeActive)
+ 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)
{
- profileConfiguration.Update();
- shouldBeActive = profileConfiguration.ActivationConditionMet;
+ if (!profileConfiguration.FadeInAndOut)
+ DeactivateProfile(profileConfiguration);
+ else if (!profileConfiguration.Profile.ShouldDisplay && profileConfiguration.Profile.Opacity <= 0)
+ DeactivateProfile(profileConfiguration);
+ else if (profileConfiguration.Profile.Opacity > 0)
+ RequestDeactivation(profileConfiguration);
}
- 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)
- {
- _updateExceptions.Add(e);
- }
+ profileConfiguration.Profile?.Update(deltaTime);
+ }
+ catch (Exception e)
+ {
+ _updateExceptions.Add(e);
}
}
-
- LogProfileUpdateExceptions();
- _pendingKeyboardEvents.Clear();
}
+
+ LogProfileUpdateExceptions();
+ _pendingKeyboardEvents.Clear();
}
///
@@ -138,78 +136,32 @@ internal class ProfileService : IProfileService
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
- {
- 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; private set; }
///
public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration)
@@ -226,21 +178,7 @@ internal class ProfileService : IProfileService
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)
@@ -279,45 +217,29 @@ internal class ProfileService : IProfileService
profileConfiguration.Profile.ShouldDisplay = false;
}
-
- ///
- public void DeleteProfile(ProfileConfiguration profileConfiguration)
- {
- DeactivateProfile(profileConfiguration);
-
- ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
- if (profileEntity == null)
- return;
-
- profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);
- _profileRepository.Remove(profileEntity);
- SaveProfileCategory(profileConfiguration.Category);
- }
-
+
///
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);
- }
- }
- else
- {
- profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
+ category.Order++;
+ category.Save();
}
- _profileCategories.Add(profileCategory);
- SaveProfileCategory(profileCategory);
+ _profileCategoryRepository.SaveRange(ProfileCategories.Select(c => c.Entity).ToList());
}
+ else
+ {
+ profileCategory = new ProfileCategory(name, ProfileCategories.Count + 1);
+ }
+
+ _profileCategoryRepository.Add(profileCategory.Entity);
+ ProfileCategories = new ReadOnlyCollection([..ProfileCategories, profileCategory]);
OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory));
return profileCategory;
@@ -326,15 +248,11 @@ internal class ProfileService : IProfileService
///
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 = new ReadOnlyCollection(ProfileCategories.Where(c => c != profileCategory).ToList());
+ _profileCategoryRepository.Remove(profileCategory.Entity);
OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory));
}
@@ -343,26 +261,24 @@ internal class ProfileService : IProfileService
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);
+ category.AddProfileConfiguration(configuration, category.ProfileConfigurations.FirstOrDefault());
+ SaveProfileCategory(category);
return configuration;
}
-
+
///
public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration)
{
- profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);
-
DeactivateProfile(profileConfiguration);
- SaveProfileCategory(profileConfiguration.Category);
- ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
- if (profileEntity != null)
- _profileRepository.Remove(profileEntity);
- profileConfiguration.Dispose();
+ ProfileCategory category = profileConfiguration.Category;
+
+ category.RemoveProfileConfiguration(profileConfiguration);
+ category.Save();
+
+ _profileRepository.Remove(profileConfiguration.Entity);
+ _profileCategoryRepository.Save(category.Entity);
}
///
@@ -370,11 +286,7 @@ internal class ProfileService : IProfileService
{
profileCategory.Save();
_profileCategoryRepository.Save(profileCategory.Entity);
-
- lock (_profileCategories)
- {
- _profileCategories.Sort((a, b) => a.Order - b.Order);
- }
+ ProfileCategories = new ReadOnlyCollection(ProfileCategories.OrderBy(c => c.Order).ToList());
}
///
@@ -392,13 +304,16 @@ internal class ProfileService : IProfileService
profile.IsFreshImport = false;
profile.ProfileEntity.IsFreshImport = false;
- _profileRepository.Save(profile.ProfileEntity);
+ _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
- 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);
ActivateProfile(localInstance);
}
@@ -406,12 +321,8 @@ internal class ProfileService : IProfileService
///
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 +341,11 @@ internal class ProfileService : IProfileService
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);
}
}
@@ -444,7 +354,7 @@ internal class ProfileService : IProfileService
}
///
- 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);
@@ -468,7 +378,7 @@ internal class ProfileService : IProfileService
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);
@@ -495,27 +405,26 @@ internal class ProfileService : IProfileService
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;
- category.AddProfileConfiguration(profileConfiguration, targetIndex);
+ // A new GUID will be given on save
+ ProfileConfiguration profileConfiguration = new(category, containerEntity);
+ if (nameAffix != null)
+ profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
+
+ profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id;
+ category.AddProfileConfiguration(profileConfiguration, target);
List modules = _pluginManagementService.GetFeaturesOfType();
profileConfiguration.LoadModules(modules);
@@ -527,9 +436,9 @@ internal class ProfileService : IProfileService
///
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);
- DeleteProfile(profileConfiguration);
+ RemoveProfileConfiguration(profileConfiguration);
SaveProfileCategory(imported.Category);
return imported;
@@ -548,60 +457,40 @@ internal class ProfileService : IProfileService
renderProfileElement.Save();
_logger.Debug("Adapt profile - Saving " + profile);
- _profileRepository.Save(profile.ProfileEntity);
+ SaveProfileCategory(profile.Configuration.Category);
}
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
{
- lock (_profileCategories)
- {
- _pendingKeyboardEvents.Add(e);
- }
+ _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
///
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.Legacy/Artemis.Storage.Legacy.csproj b/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj
new file mode 100644
index 000000000..2da8bd8c3
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Artemis.Storage.Legacy.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
diff --git a/src/Artemis.Storage/Entities/General/QueuedActionEntity.cs b/src/Artemis.Storage.Legacy/Entities/General/QueuedActionEntity.cs
similarity index 70%
rename from src/Artemis.Storage/Entities/General/QueuedActionEntity.cs
rename to src/Artemis.Storage.Legacy/Entities/General/QueuedActionEntity.cs
index 942a6501d..90770d565 100644
--- a/src/Artemis.Storage/Entities/General/QueuedActionEntity.cs
+++ b/src/Artemis.Storage.Legacy/Entities/General/QueuedActionEntity.cs
@@ -1,9 +1,6 @@
-using System;
-using System.Collections.Generic;
+namespace Artemis.Storage.Legacy.Entities.General;
-namespace Artemis.Storage.Entities.General;
-
-public class QueuedActionEntity
+internal class QueuedActionEntity
{
public QueuedActionEntity()
{
diff --git a/src/Artemis.Storage.Legacy/Entities/General/ReleaseEntity.cs b/src/Artemis.Storage.Legacy/Entities/General/ReleaseEntity.cs
new file mode 100644
index 000000000..a8aea26a8
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/General/ReleaseEntity.cs
@@ -0,0 +1,19 @@
+namespace Artemis.Storage.Legacy.Entities.General;
+
+internal 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.Legacy/Entities/General/ScriptConfigurationEntity.cs b/src/Artemis.Storage.Legacy/Entities/General/ScriptConfigurationEntity.cs
new file mode 100644
index 000000000..94fce07e4
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/General/ScriptConfigurationEntity.cs
@@ -0,0 +1,10 @@
+namespace Artemis.Storage.Legacy.Entities.General;
+
+internal 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.Legacy/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginEntity.cs
new file mode 100644
index 000000000..47e2ba74b
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginEntity.cs
@@ -0,0 +1,45 @@
+namespace Artemis.Storage.Legacy.Entities.Plugins;
+
+///
+/// Represents the configuration of a plugin, each plugin has one configuration
+///
+internal 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()
+ {
+ PluginGuid = Id,
+ IsEnabled = IsEnabled,
+ Features = Features.Select(f => f.Migrate()).ToList()
+ };
+ }
+}
+
+///
+/// Represents the configuration of a plugin feature, each feature has one configuration
+///
+internal 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.Legacy/Entities/Plugins/PluginSettingEntity.cs b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginSettingEntity.cs
new file mode 100644
index 000000000..587e7d1c8
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Plugins/PluginSettingEntity.cs
@@ -0,0 +1,24 @@
+namespace Artemis.Storage.Legacy.Entities.Plugins;
+
+///
+/// Represents the setting of a plugin, a plugin can have multiple settings
+///
+internal 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.Legacy/Entities/Profile/Abstract/RenderElementEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Abstract/RenderElementEntity.cs
new file mode 100644
index 000000000..56b4e97fd
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/Abstract/RenderElementEntity.cs
@@ -0,0 +1,14 @@
+using Artemis.Storage.Legacy.Entities.Profile.Conditions;
+
+namespace Artemis.Storage.Legacy.Entities.Profile.Abstract;
+
+internal 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.Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs
new file mode 100644
index 000000000..16ceb4d79
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/CategoryAdaptionHintEntity.cs
@@ -0,0 +1,10 @@
+namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints;
+
+internal 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.Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs
new file mode 100644
index 000000000..dc4005707
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/DeviceAdaptionHintEntity.cs
@@ -0,0 +1,10 @@
+namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints;
+
+internal 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.Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs
new file mode 100644
index 000000000..a18f3b6ac
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/IAdaptionHintEntity.cs
@@ -0,0 +1,9 @@
+using System.Text.Json.Serialization;
+
+namespace Artemis.Storage.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.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.Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs
new file mode 100644
index 000000000..9d03535d3
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/AdaptionHints/SingleLedAdaptionHintEntity.cs
@@ -0,0 +1,10 @@
+namespace Artemis.Storage.Legacy.Entities.Profile.AdaptionHints;
+
+internal 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.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.Legacy/Entities/Profile/Conditions/EventConditionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/EventConditionEntity.cs
new file mode 100644
index 000000000..574d5d42e
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/EventConditionEntity.cs
@@ -0,0 +1,12 @@
+using Artemis.Storage.Legacy.Entities.Profile.Nodes;
+
+namespace Artemis.Storage.Legacy.Entities.Profile.Conditions;
+
+internal 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.Legacy/Entities/Profile/Conditions/IConditionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/IConditionEntity.cs
new file mode 100644
index 000000000..ab5086aa9
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/Conditions/IConditionEntity.cs
@@ -0,0 +1,9 @@
+using System.Text.Json.Serialization;
+
+namespace Artemis.Storage.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.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.Legacy/Entities/Profile/DataModelPathEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/DataModelPathEntity.cs
new file mode 100644
index 000000000..4ebef8bc4
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/DataModelPathEntity.cs
@@ -0,0 +1,8 @@
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/FolderEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/FolderEntity.cs
new file mode 100644
index 000000000..ae467caa7
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/FolderEntity.cs
@@ -0,0 +1,17 @@
+using Artemis.Storage.Legacy.Entities.Profile.Abstract;
+using LiteDB;
+
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/KeyframeEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/KeyframeEntity.cs
new file mode 100644
index 000000000..6f75fe047
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/KeyframeEntity.cs
@@ -0,0 +1,9 @@
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/LayerBrushEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LayerBrushEntity.cs
new file mode 100644
index 000000000..254c19e71
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/LayerBrushEntity.cs
@@ -0,0 +1,9 @@
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/LayerEffectEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEffectEntity.cs
new file mode 100644
index 000000000..7f4c7e696
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEffectEntity.cs
@@ -0,0 +1,12 @@
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/LayerEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEntity.cs
new file mode 100644
index 000000000..d00a62c20
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/LayerEntity.cs
@@ -0,0 +1,30 @@
+using Artemis.Storage.Legacy.Entities.Profile.Abstract;
+using Artemis.Storage.Legacy.Entities.Profile.AdaptionHints;
+using LiteDB;
+
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/LedEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/LedEntity.cs
new file mode 100644
index 000000000..5f7328bd6
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/LedEntity.cs
@@ -0,0 +1,36 @@
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs
new file mode 100644
index 000000000..c186db470
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeConnectionEntity.cs
@@ -0,0 +1,29 @@
+namespace Artemis.Storage.Legacy.Entities.Profile.Nodes;
+
+internal 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.Legacy/Entities/Profile/Nodes/NodeEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeEntity.cs
new file mode 100644
index 000000000..314964430
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeEntity.cs
@@ -0,0 +1,38 @@
+namespace Artemis.Storage.Legacy.Entities.Profile.Nodes;
+
+internal 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.Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs
new file mode 100644
index 000000000..b10944b48
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodePinCollectionEntity.cs
@@ -0,0 +1,19 @@
+namespace Artemis.Storage.Legacy.Entities.Profile.Nodes;
+
+internal 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.Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs
new file mode 100644
index 000000000..8da434571
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/Nodes/NodeScriptEntity.cs
@@ -0,0 +1,16 @@
+namespace Artemis.Storage.Legacy.Entities.Profile.Nodes;
+
+internal 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.Legacy/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileCategoryEntity.cs
new file mode 100644
index 000000000..c5b61012b
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileCategoryEntity.cs
@@ -0,0 +1,78 @@
+using Artemis.Core;
+using Artemis.Storage.Entities.Profile;
+using LiteDB;
+using Serilog;
+
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/ProfileConfigurationEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationEntity.cs
new file mode 100644
index 000000000..482672a60
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileConfigurationEntity.cs
@@ -0,0 +1,29 @@
+using Artemis.Storage.Legacy.Entities.Profile.Nodes;
+
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.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.Legacy/Entities/Profile/ProfileEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileEntity.cs
new file mode 100644
index 000000000..dbbbfee9d
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/ProfileEntity.cs
@@ -0,0 +1,32 @@
+using Artemis.Storage.Legacy.Entities.General;
+
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/PropertyEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyEntity.cs
new file mode 100644
index 000000000..44a4406ab
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyEntity.cs
@@ -0,0 +1,13 @@
+using Artemis.Storage.Legacy.Entities.Profile.DataBindings;
+
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/PropertyGroupEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyGroupEntity.cs
new file mode 100644
index 000000000..2c7f316bb
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/PropertyGroupEntity.cs
@@ -0,0 +1,8 @@
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Profile/TimelineEntity.cs b/src/Artemis.Storage.Legacy/Entities/Profile/TimelineEntity.cs
new file mode 100644
index 000000000..9a1f400e4
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Profile/TimelineEntity.cs
@@ -0,0 +1,8 @@
+namespace Artemis.Storage.Legacy.Entities.Profile;
+
+internal 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.Legacy/Entities/Surface/DeviceEntity.cs b/src/Artemis.Storage.Legacy/Entities/Surface/DeviceEntity.cs
new file mode 100644
index 000000000..fc3988dba
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Surface/DeviceEntity.cs
@@ -0,0 +1,78 @@
+namespace Artemis.Storage.Legacy.Entities.Surface;
+
+internal 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
+ };
+ }
+}
+
+internal class InputMappingEntity
+{
+ public int OriginalLedId { get; set; }
+ public int MappedLedId { get; set; }
+}
+
+internal 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.Legacy/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs
new file mode 100644
index 000000000..24c7a2efb
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Entities/Workshop/EntryEntity.cs
@@ -0,0 +1,38 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace Artemis.Storage.Legacy.Entities.Workshop;
+
+internal 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?.ToDictionary(kvp => kvp.Key, kvp => JsonSerializer.SerializeToNode(kvp.Value) ?? new JsonObject())
+ };
+ }
+}
\ No newline at end of file
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.Legacy/Migrations/IProfileMigration.cs b/src/Artemis.Storage.Legacy/Migrations/IProfileMigration.cs
new file mode 100644
index 000000000..53d1d1eda
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Migrations/IProfileMigration.cs
@@ -0,0 +1,9 @@
+using System.Text.Json.Nodes;
+
+namespace Artemis.Storage.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/Migrations/IStorageMigration.cs b/src/Artemis.Storage.Legacy/Migrations/IStorageMigration.cs
similarity index 74%
rename from src/Artemis.Storage/Migrations/IStorageMigration.cs
rename to src/Artemis.Storage.Legacy/Migrations/IStorageMigration.cs
index 4382a837d..18ba559ca 100644
--- a/src/Artemis.Storage/Migrations/IStorageMigration.cs
+++ b/src/Artemis.Storage.Legacy/Migrations/IStorageMigration.cs
@@ -1,6 +1,6 @@
using LiteDB;
-namespace Artemis.Storage.Migrations;
+namespace Artemis.Storage.Legacy.Migrations;
public interface IStorageMigration
{
diff --git a/src/Artemis.Storage/Migrations/Storage/M0020AvaloniaReset.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0020AvaloniaReset.cs
similarity index 67%
rename from src/Artemis.Storage/Migrations/Storage/M0020AvaloniaReset.cs
rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0020AvaloniaReset.cs
index 293fc1ff7..685585ffb 100644
--- a/src/Artemis.Storage/Migrations/Storage/M0020AvaloniaReset.cs
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0020AvaloniaReset.cs
@@ -1,10 +1,8 @@
-using System.Collections.Generic;
-using System.Linq;
-using LiteDB;
+using LiteDB;
-namespace Artemis.Storage.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/Migrations/Storage/M0021GradientNodes.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0021GradientNodes.cs
similarity index 91%
rename from src/Artemis.Storage/Migrations/Storage/M0021GradientNodes.cs
rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0021GradientNodes.cs
index 2bb90a45c..02998e98c 100644
--- a/src/Artemis.Storage/Migrations/Storage/M0021GradientNodes.cs
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0021GradientNodes.cs
@@ -1,13 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Artemis.Storage.Entities.Profile;
-using Artemis.Storage.Entities.Profile.Nodes;
+using Artemis.Storage.Legacy.Entities.Profile;
+using Artemis.Storage.Legacy.Entities.Profile.Nodes;
using LiteDB;
-namespace Artemis.Storage.Migrations.Storage;
+namespace Artemis.Storage.Legacy.Migrations.Storage;
-public class M0021GradientNodes : IStorageMigration
+internal class M0021GradientNodes : IStorageMigration
{
private void MigrateDataBinding(PropertyEntity property)
{
@@ -62,7 +59,7 @@ public class M0021GradientNodes : IStorageMigration
{
if (propertyGroup == null)
return;
-
+
foreach (PropertyGroupEntity propertyGroupPropertyGroup in propertyGroup.PropertyGroups)
MigrateDataBinding(propertyGroupPropertyGroup);
diff --git a/src/Artemis.Storage/Migrations/Storage/M0022TransitionNodes.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0022TransitionNodes.cs
similarity index 90%
rename from src/Artemis.Storage/Migrations/Storage/M0022TransitionNodes.cs
rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0022TransitionNodes.cs
index 791b95ea9..369ab130c 100644
--- a/src/Artemis.Storage/Migrations/Storage/M0022TransitionNodes.cs
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0022TransitionNodes.cs
@@ -1,13 +1,11 @@
-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 Artemis.Storage.Legacy.Entities.Profile;
+using Artemis.Storage.Legacy.Entities.Profile.Conditions;
+using Artemis.Storage.Legacy.Entities.Profile.Nodes;
using LiteDB;
-namespace Artemis.Storage.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/Migrations/Storage/M0023LayoutProviders.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0023LayoutProviders.cs
similarity index 86%
rename from src/Artemis.Storage/Migrations/Storage/M0023LayoutProviders.cs
rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0023LayoutProviders.cs
index 5296d5842..1a8d7500b 100644
--- a/src/Artemis.Storage/Migrations/Storage/M0023LayoutProviders.cs
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0023LayoutProviders.cs
@@ -1,9 +1,8 @@
-using System.Collections.Generic;
-using LiteDB;
+using LiteDB;
-namespace Artemis.Storage.Migrations.Storage;
+namespace Artemis.Storage.Legacy.Migrations.Storage;
-public class M0023LayoutProviders : IStorageMigration
+internal class M0023LayoutProviders : IStorageMigration
{
public int UserVersion => 23;
@@ -20,9 +19,13 @@ public class M0023LayoutProviders : IStorageMigration
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/Migrations/Storage/M0024NodeProviders.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0024NodeProviders.cs
similarity index 91%
rename from src/Artemis.Storage/Migrations/Storage/M0024NodeProviders.cs
rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0024NodeProviders.cs
index c09c0f1fc..4617ae6d1 100644
--- a/src/Artemis.Storage/Migrations/Storage/M0024NodeProviders.cs
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0024NodeProviders.cs
@@ -1,10 +1,8 @@
-using System.Collections.Generic;
-using Artemis.Storage.Entities.Profile;
-using LiteDB;
+using LiteDB;
-namespace Artemis.Storage.Migrations.Storage;
+namespace Artemis.Storage.Legacy.Migrations.Storage;
-public class M0024NodeProviders : IStorageMigration
+internal class M0024NodeProviders : IStorageMigration
{
public int UserVersion => 24;
@@ -22,6 +20,7 @@ public class M0024NodeProviders : IStorageMigration
categoriesToUpdate.Add(profileCategoryBson);
}
}
+
categoryCollection.Update(categoriesToUpdate);
ILiteCollection collection = repository.Database.GetCollection("ProfileEntity");
@@ -30,15 +29,12 @@ public class M0024NodeProviders : IStorageMigration
{
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);
@@ -46,8 +42,7 @@ public class M0024NodeProviders : IStorageMigration
MigratePropertyGroup(layer.AsDocument["TransformPropertyGroup"].AsDocument);
MigratePropertyGroup(layer.AsDocument["LayerBrush"]?["PropertyGroup"].AsDocument);
}
- }
-
+
profilesToUpdate.Add(profileBson);
}
@@ -58,10 +53,8 @@ public class M0024NodeProviders : IStorageMigration
{
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)
@@ -77,16 +70,12 @@ public class M0024NodeProviders : IStorageMigration
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/Migrations/Storage/M0025NodeProvidersProfileConfig.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs
similarity index 89%
rename from src/Artemis.Storage/Migrations/Storage/M0025NodeProvidersProfileConfig.cs
rename to src/Artemis.Storage.Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs
index 46d8c3fe7..bff6ff6ff 100644
--- a/src/Artemis.Storage/Migrations/Storage/M0025NodeProvidersProfileConfig.cs
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0025NodeProvidersProfileConfig.cs
@@ -1,9 +1,8 @@
-using System.Collections.Generic;
-using LiteDB;
+using LiteDB;
-namespace Artemis.Storage.Migrations.Storage;
+namespace Artemis.Storage.Legacy.Migrations.Storage;
-public class M0025NodeProvidersProfileConfig : IStorageMigration
+internal class M0025NodeProvidersProfileConfig : IStorageMigration
{
public int UserVersion => 25;
@@ -21,13 +20,14 @@ public class M0025NodeProvidersProfileConfig : IStorageMigration
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.Legacy/Migrations/Storage/M0026NodeStorage.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0026NodeStorage.cs
new file mode 100644
index 000000000..cbea0db8d
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0026NodeStorage.cs
@@ -0,0 +1,215 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using LiteDB;
+using Serilog;
+
+namespace Artemis.Storage.Legacy.Migrations.Storage;
+
+internal 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.Legacy/Migrations/Storage/M0027Namespace.cs b/src/Artemis.Storage.Legacy/Migrations/Storage/M0027Namespace.cs
new file mode 100644
index 000000000..b0bee99a1
--- /dev/null
+++ b/src/Artemis.Storage.Legacy/Migrations/Storage/M0027Namespace.cs
@@ -0,0 +1,41 @@
+using LiteDB;
+
+namespace Artemis.Storage.Legacy.Migrations.Storage;
+
+internal 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", "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/Artemis.Storage.csproj b/src/Artemis.Storage/Artemis.Storage.csproj
index b01c7ef62..fa7b8fe61 100644
--- a/src/Artemis.Storage/Artemis.Storage.csproj
+++ b/src/Artemis.Storage/Artemis.Storage.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/src/Artemis.Storage/ArtemisDbContext.cs b/src/Artemis.Storage/ArtemisDbContext.cs
new file mode 100644
index 000000000..32acba6a6
--- /dev/null
+++ b/src/Artemis.Storage/ArtemisDbContext.cs
@@ -0,0 +1,59 @@
+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;
+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 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;
+ 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()
+ .Property(e => e.Metadata)
+ .HasConversion(
+ v => JsonSerializer.Serialize(v, JsonSerializerOptions),
+ v => JsonSerializer.Deserialize>(v, JsonSerializerOptions) ?? new Dictionary());
+
+ modelBuilder.Entity()
+ .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/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 611200781..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 class PluginEntity
}
public Guid Id { get; set; }
+ public Guid PluginGuid { get; set; }
public bool IsEnabled { get; set; }
public List Features { get; set; }
@@ -24,6 +28,8 @@ public class 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/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 class LayerEntity : RenderElementEntity
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/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs
index 126845b38..b7a3c15f4 100644
--- a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs
+++ b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs
@@ -1,16 +1,20 @@
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; }
public int Order { get; set; }
- public List ProfileConfigurations { get; set; } = new();
+ public List ProfileConfigurations { 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..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; }
@@ -27,5 +25,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/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/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 5075131c2..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 class 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 class 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; }
@@ -42,5 +53,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..ad11d6188 100644
--- a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs
+++ b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs
@@ -1,21 +1,24 @@
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/20240310201706_Initial.Designer.cs b/src/Artemis.Storage/Migrations/20240310201706_Initial.Designer.cs
new file mode 100644
index 000000000..bb62ee588
--- /dev/null
+++ b/src/Artemis.Storage/Migrations/20240310201706_Initial.Designer.cs
@@ -0,0 +1,352 @@
+//
+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("20240310201706_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()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstalledAt");
+
+ b.HasIndex("Version")
+ .IsUnique();
+
+ b.ToTable("Releases");
+ });
+
+ modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("IsEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("PluginGuid")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PluginGuid")
+ .IsUnique();
+
+ 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("PluginFeatures");
+ });
+
+ modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("PluginGuid")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PluginGuid");
+
+ b.HasIndex("Name", "PluginGuid")
+ .IsUnique();
+
+ 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()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ 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("ProfileContainers");
+ });
+
+ modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
+ {
+ b.Property("Id")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("BlueScale")
+ .HasColumnType("REAL");
+
+ b.Property("Categories")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("DeviceProvider")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("GreenScale")
+ .HasColumnType("REAL");
+
+ b.Property("IsEnabled")
+ .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")
+ .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")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ReleaseId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReleaseVersion")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EntryId")
+ .IsUnique();
+
+ 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/20240310201706_Initial.cs b/src/Artemis.Storage/Migrations/20240310201706_Initial.cs
new file mode 100644
index 000000000..286f29922
--- /dev/null
+++ b/src/Artemis.Storage/Migrations/20240310201706_Initial.cs
@@ -0,0 +1,235 @@
+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", 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),
+ 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", 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)
+ },
+ 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: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Entries", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Plugins",
+ 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 =>
+ {
+ 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", maxLength: 128, 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", 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)
+ },
+ 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", maxLength: 64, nullable: false),
+ InstalledAt = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Releases", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "PluginFeatures",
+ 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_PluginFeatures", x => x.Id);
+ table.ForeignKey(
+ name: "FK_PluginFeatures_Plugins_PluginEntityId",
+ column: x => x.PluginEntityId,
+ principalTable: "Plugins",
+ principalColumn: "Id");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ProfileContainers",
+ 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_ProfileContainers", x => x.Id);
+ table.ForeignKey(
+ name: "FK_ProfileContainers_ProfileCategories_ProfileCategoryId",
+ column: x => x.ProfileCategoryId,
+ principalTable: "ProfileCategories",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Entries_EntryId",
+ table: "Entries",
+ column: "EntryId",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_PluginFeatures_PluginEntityId",
+ table: "PluginFeatures",
+ column: "PluginEntityId");
+
+ migrationBuilder.CreateIndex(
+ 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);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "Devices");
+
+ migrationBuilder.DropTable(
+ name: "Entries");
+
+ migrationBuilder.DropTable(
+ name: "PluginFeatures");
+
+ migrationBuilder.DropTable(
+ name: "PluginSettings");
+
+ migrationBuilder.DropTable(
+ name: "ProfileContainers");
+
+ 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..270d88797
--- /dev/null
+++ b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs
@@ -0,0 +1,349 @@
+//
+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()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("InstalledAt");
+
+ b.HasIndex("Version")
+ .IsUnique();
+
+ b.ToTable("Releases");
+ });
+
+ modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("IsEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("PluginGuid")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PluginGuid")
+ .IsUnique();
+
+ 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("PluginFeatures");
+ });
+
+ modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT");
+
+ b.Property("PluginGuid")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PluginGuid");
+
+ b.HasIndex("Name", "PluginGuid")
+ .IsUnique();
+
+ 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()
+ .HasMaxLength(64)
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ 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("ProfileContainers");
+ });
+
+ modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
+ {
+ b.Property("Id")
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("BlueScale")
+ .HasColumnType("REAL");
+
+ b.Property("Categories")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("DeviceProvider")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("TEXT");
+
+ b.Property("GreenScale")
+ .HasColumnType("REAL");
+
+ b.Property("IsEnabled")
+ .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")
+ .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")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.Property("ReleaseId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReleaseVersion")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("EntryId")
+ .IsUnique();
+
+ 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/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..9e7353a3c 100644
--- a/src/Artemis.Storage/Repositories/DeviceRepository.cs
+++ b/src/Artemis.Storage/Repositories/DeviceRepository.cs
@@ -1,47 +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;
-using LiteDB;
namespace Artemis.Storage.Repositories;
-internal class DeviceRepository : IDeviceRepository
+internal class DeviceRepository(Func getContext) : IDeviceRepository
{
- private readonly LiteRepository _repository;
-
- public DeviceRepository(LiteRepository repository)
- {
- _repository = repository;
- _repository.Database.GetCollection().EnsureIndex(s => s.Id);
- }
-
public void Add(DeviceEntity deviceEntity)
{
- _repository.Insert(deviceEntity);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.Devices.Add(deviceEntity);
+ dbContext.SaveChanges();
}
public void Remove(DeviceEntity deviceEntity)
{
- _repository.Delete(deviceEntity.Id);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.Devices.Remove(deviceEntity);
+ dbContext.SaveChanges();
}
public DeviceEntity? Get(string id)
{
- return _repository.FirstOrDefault(s => s.Id == id);
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.Devices.FirstOrDefault(d => d.Id == id);
}
public List GetAll()
{
- return _repository.Query().Include(s => s.InputIdentifiers).ToList();
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.Devices.ToList();
}
-
+
public void Save(DeviceEntity deviceEntity)
{
- _repository.Upsert(deviceEntity);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.Update(deviceEntity);
+ dbContext.SaveChanges();
}
-
- public void Save(IEnumerable deviceEntities)
+
+ public void SaveRange(IEnumerable deviceEntities)
{
- _repository.Upsert(deviceEntities);
+ 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 81775f967..43a155e66 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
+internal class EntryRepository(Func getContext) : IEntryRepository
{
- private readonly LiteRepository _repository;
-
- public EntryRepository(LiteRepository repository)
- {
- _repository = repository;
- _repository.Database.GetCollection().EnsureIndex(s => s.Id);
- _repository.Database.GetCollection().EnsureIndex(s => s.EntryId);
- }
-
public void Add(EntryEntity entryEntity)
{
- _repository.Insert(entryEntity);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.Entries.Add(entryEntity);
+ dbContext.SaveChanges();
}
public void Remove(EntryEntity entryEntity)
{
- _repository.Delete(entryEntity.Id);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.Entries.Remove(entryEntity);
+ dbContext.SaveChanges();
}
public EntryEntity? Get(Guid id)
{
- return _repository.FirstOrDefault(s => s.Id == id);
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.Entries.FirstOrDefault(s => s.Id == id);
}
public EntryEntity? GetByEntryId(long entryId)
{
- return _repository.FirstOrDefault(s => s.EntryId == entryId);
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.Entries.FirstOrDefault(s => s.EntryId == entryId);
}
public List GetAll()
{
- return _repository.Query().ToList();
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.Entries.ToList();
}
-
+
public void Save(EntryEntity entryEntity)
{
- _repository.Upsert(entryEntity);
- }
-
- public void Save(IEnumerable entryEntities)
- {
- _repository.Upsert(entryEntities);
+ 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 50fd1df86..52e5eec15 100644
--- a/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs
+++ b/src/Artemis.Storage/Repositories/Interfaces/IDeviceRepository.cs
@@ -10,5 +10,5 @@ public interface IDeviceRepository : IRepository
DeviceEntity? Get(string id);
List GetAll();
void Save(DeviceEntity deviceEntity);
- void Save(IEnumerable deviceEntities);
+ 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 59d610f95..586542692 100644
--- a/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs
+++ b/src/Artemis.Storage/Repositories/Interfaces/IEntryRepository.cs
@@ -12,5 +12,4 @@ public interface IEntryRepository : IRepository
EntryEntity? GetByEntryId(long entryId);
List GetAll();
void Save(EntryEntity entryEntity);
- void Save(IEnumerable entryEntities);
}
\ 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..cb25caf5d 100644
--- a/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs
+++ b/src/Artemis.Storage/Repositories/Interfaces/IPluginRepository.cs
@@ -5,13 +5,9 @@ namespace Artemis.Storage.Repositories.Interfaces;
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);
+ PluginEntity? GetPluginByPluginGuid(Guid pluginGuid);
void SaveSetting(PluginSettingEntity pluginSettingEntity);
+ void SavePlugin(PluginEntity pluginEntity);
+ PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid);
void RemoveSettings(Guid pluginGuid);
}
\ 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..96ca0d7d2 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;
@@ -11,8 +10,7 @@ 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);
+ bool IsUnique(string name, Guid? id);
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
index 8430b82c4..bc26090a5 100644
--- a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs
+++ b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs
@@ -1,14 +1,15 @@
-using System;
-using System.Collections.Generic;
+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(ProfileEntity profileEntity);
- void Remove(ProfileEntity profileEntity);
- List GetAll();
- ProfileEntity? Get(Guid id);
- void Save(ProfileEntity profileEntity);
+ 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/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/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 02a8985c0..abb335893 100644
--- a/src/Artemis.Storage/Repositories/PluginRepository.cs
+++ b/src/Artemis.Storage/Repositories/PluginRepository.cs
@@ -1,59 +1,44 @@
using System;
+using System.Linq;
using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces;
-using LiteDB;
+using Microsoft.EntityFrameworkCore;
namespace Artemis.Storage.Repositories;
-internal class PluginRepository : IPluginRepository
+internal class PluginRepository(Func getContext) : IPluginRepository
{
- private readonly LiteRepository _repository;
-
- public PluginRepository(LiteRepository repository)
+ public PluginEntity? GetPluginByPluginGuid(Guid pluginGuid)
{
- _repository = repository;
-
- _repository.Database.GetCollection().EnsureIndex(s => new {s.Name, s.PluginGuid}, true);
- }
-
- public void AddPlugin(PluginEntity pluginEntity)
- {
- _repository.Insert(pluginEntity);
- }
-
- public PluginEntity? GetPluginByGuid(Guid pluginGuid)
- {
- return _repository.FirstOrDefault(p => p.Id == pluginGuid);
- }
-
- public void SavePlugin(PluginEntity pluginEntity)
- {
- _repository.Upsert(pluginEntity);
- }
-
- public void AddSetting(PluginSettingEntity pluginSettingEntity)
- {
- _repository.Insert(pluginSettingEntity);
- }
-
- public PluginSettingEntity? GetSettingByGuid(Guid pluginGuid)
- {
- return _repository.FirstOrDefault(p => p.PluginGuid == pluginGuid);
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.Plugins.Include(p => p.Features).FirstOrDefault(p => p.PluginGuid == pluginGuid);
}
public PluginSettingEntity? GetSettingByNameAndGuid(string name, Guid pluginGuid)
{
- return _repository.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid);
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.PluginSettings.FirstOrDefault(p => p.Name == name && p.PluginGuid == pluginGuid);
}
- public void SaveSetting(PluginSettingEntity pluginSettingEntity)
- {
- _repository.Upsert(pluginSettingEntity);
- }
-
- ///
public void RemoveSettings(Guid pluginGuid)
{
- _repository.DeleteMany(s => s.PluginGuid == pluginGuid);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.PluginSettings.RemoveRange(dbContext.PluginSettings.Where(s => s.PluginGuid == pluginGuid));
+ dbContext.SaveChanges();
}
+
+ public void SaveSetting(PluginSettingEntity pluginSettingEntity)
+ {
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.PluginSettings.Update(pluginSettingEntity);
+ dbContext.SaveChanges();
+ }
+
+ public void SavePlugin(PluginEntity pluginEntity)
+ {
+ 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 8036b9d7a..5ad5ae4d7 100644
--- a/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs
+++ b/src/Artemis.Storage/Repositories/ProfileCategoryRepository.cs
@@ -1,93 +1,75 @@
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
+internal class ProfileCategoryRepository(Func getContext, IProfileRepository profileRepository) : IProfileCategoryRepository
{
- private readonly ILiteStorage _profileIcons;
- private readonly LiteRepository _repository;
-
- public ProfileCategoryRepository(LiteRepository repository)
- {
- _repository = repository;
- _repository.Database.GetCollection().EnsureIndex(s => s.Name, true);
- _profileIcons = _repository.Database.GetStorage("profileIcons");
- }
+ private bool _migratedProfiles;
public void Add(ProfileCategoryEntity profileCategoryEntity)
{
- _repository.Insert(profileCategoryEntity);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.ProfileCategories.Add(profileCategoryEntity);
+ dbContext.SaveChanges();
}
public void Remove(ProfileCategoryEntity profileCategoryEntity)
{
- _repository.Delete(profileCategoryEntity.Id);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.ProfileCategories.Remove(profileCategoryEntity);
+ dbContext.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);
+ if (!_migratedProfiles)
+ {
+ profileRepository.MigrateProfiles();
+ _migratedProfiles = true;
+ }
- return categories;
+ using ArtemisDbContext dbContext = getContext();
+ 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;
+ if (!_migratedProfiles)
+ {
+ profileRepository.MigrateProfiles();
+ _migratedProfiles = true;
+ }
- // 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;
- }
-
- public ProfileCategoryEntity 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);
+ using ArtemisDbContext dbContext = getContext();
+ return dbContext.ProfileCategories.Include(c => c.ProfileConfigurations).FirstOrDefault(c => c.Id == id);
}
public void Save(ProfileCategoryEntity profileCategoryEntity)
{
- _repository.Upsert(profileCategoryEntity);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.Update(profileCategoryEntity);
+ dbContext.SaveChanges();
}
- public Stream? GetProfileIconStream(Guid id)
+ public void SaveRange(List profileCategoryEntities)
{
- if (!_profileIcons.Exists(id))
- return null;
-
- MemoryStream stream = new();
- _profileIcons.Download(id, stream);
- return stream;
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.UpdateRange(profileCategoryEntities);
+ dbContext.SaveChanges();
}
- public void SaveProfileIconStream(ProfileConfigurationEntity profileConfigurationEntity, Stream stream)
+ public bool IsUnique(string name, Guid? id)
{
- if (profileConfigurationEntity.FileIconId == Guid.Empty)
- profileConfigurationEntity.FileIconId = Guid.NewGuid();
+ using ArtemisDbContext dbContext = getContext();
- if (stream == null && _profileIcons.Exists(profileConfigurationEntity.FileIconId))
- _profileIcons.Delete(profileConfigurationEntity.FileIconId);
-
- _profileIcons.Upload(profileConfigurationEntity.FileIconId, profileConfigurationEntity.FileIconId + ".png", stream);
- }
-
- private static void UpdateProfileVersions(ProfileCategoryEntity profileCategoryEntity)
- {
- foreach (ProfileConfigurationEntity profileConfigurationEntity in profileCategoryEntity.ProfileConfigurations)
- profileConfigurationEntity.Version = StorageMigrationService.PROFILE_VERSION;
+ 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
index 738099a32..84e72cb2b 100644
--- a/src/Artemis.Storage/Repositories/ProfileRepository.cs
+++ b/src/Artemis.Storage/Repositories/ProfileRepository.cs
@@ -1,43 +1,104 @@
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 LiteDB;
+using Microsoft.EntityFrameworkCore;
+using Serilog;
namespace Artemis.Storage.Repositories;
-internal class ProfileRepository : IProfileRepository
+public class ProfileRepository(ILogger logger, Func getContext, List profileMigrators) : IProfileRepository
{
- private readonly LiteRepository _repository;
-
- public ProfileRepository(LiteRepository repository)
+ public void Add(ProfileContainerEntity profileContainerEntity)
{
- _repository = repository;
- _repository.Database.GetCollection().EnsureIndex(s => s.Name);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.ProfileContainers.Add(profileContainerEntity);
+ dbContext.SaveChanges();
}
- public void Add(ProfileEntity profileEntity)
+ public void Remove(ProfileContainerEntity profileContainerEntity)
{
- _repository.Insert(profileEntity);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.ProfileContainers.Remove(profileContainerEntity);
+ dbContext.SaveChanges();
}
- public void Remove(ProfileEntity profileEntity)
+ public void Save(ProfileContainerEntity profileContainerEntity)
{
- _repository.Delete(profileEntity.Id);
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.Update(profileContainerEntity);
+ dbContext.SaveChanges();
}
- public List GetAll()
+ public void SaveRange(List profileContainerEntities)
{
- return _repository.Query().ToList();
+ using ArtemisDbContext dbContext = getContext();
+ dbContext.UpdateRange(profileContainerEntities);
+ dbContext.SaveChanges();
}
- public ProfileEntity? Get(Guid id)
+ public void MigrateProfiles()
{
- return _repository.FirstOrDefault(p => p.Id == id);
+ 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)
+ {
+ 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);
+ }
+ }
}
- public void Save(ProfileEntity profileEntity)
+ public void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson)
{
- _repository.Upsert(profileEntity);
+ if (configurationJson == null || profileJson == null)
+ return;
+
+ configurationJson["Version"] ??= 0;
+
+ foreach (IProfileMigration profileMigrator in profileMigrators.OrderBy(m => m.Version))
+ {
+ if (profileMigrator.Version <= configurationJson["Version"]!.GetValue