1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-31 09:43:46 +00:00

Storage - Got it to build and even run

This commit is contained in:
RobertBeekman 2024-03-08 21:44:28 +01:00
parent 10850adb24
commit 41a7543778
36 changed files with 1109 additions and 378 deletions

View File

@ -31,8 +31,9 @@ public static class ContainerExtensions
// Bind storage // Bind storage
container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton); container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton);
container.RegisterDelegate(() => StorageManager.CreateDbContext(Constants.DataFolder), Reuse.Transient);
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IRepository>(), Reuse.Singleton); container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IRepository>(), Reuse.Singleton);
// Bind migrations // Bind migrations
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IProfileMigration>(), Reuse.Singleton, nonPublicServiceTypes: true); container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IProfileMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);

View File

@ -175,7 +175,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
Order = Entity.Order; Order = Entity.Order;
_profileConfigurations.Clear(); _profileConfigurations.Clear();
foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations) foreach (ProfileContainerEntity entityProfileConfiguration in Entity.ProfileConfigurations)
_profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration)); _profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration));
} }
@ -186,13 +186,10 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
Entity.IsCollapsed = IsCollapsed; Entity.IsCollapsed = IsCollapsed;
Entity.IsSuspended = IsSuspended; Entity.IsSuspended = IsSuspended;
Entity.Order = Order; Entity.Order = Order;
Entity.ProfileConfigurations.Clear(); Entity.ProfileConfigurations.Clear();
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) foreach (ProfileConfiguration profileConfiguration in _profileConfigurations)
{
profileConfiguration.Save();
Entity.ProfileConfigurations.Add(profileConfiguration.Entity); Entity.ProfileConfigurations.Add(profileConfiguration.Entity);
}
} }
#endregion #endregion

View File

@ -37,13 +37,13 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
_name = name; _name = name;
_category = category; _category = category;
Entity = new ProfileConfigurationEntity(); Entity = new ProfileContainerEntity();
Icon = new ProfileConfigurationIcon(Entity); Icon = new ProfileConfigurationIcon(Entity);
Icon.SetIconByName(icon); Icon.SetIconByName(icon);
ActivationCondition = new NodeScript<bool>("Activate profile", "Whether or not the profile should be active", this); ActivationCondition = new NodeScript<bool>("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 // Will be loaded from the entity
_name = null!; _name = null!;
@ -192,12 +192,12 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
/// <summary> /// <summary>
/// Gets the entity used by this profile config /// Gets the entity used by this profile config
/// </summary> /// </summary>
public ProfileConfigurationEntity Entity { get; } public ProfileContainerEntity Entity { get; }
/// <summary> /// <summary>
/// Gets the ID of the profile of this profile configuration /// Gets the ID of the profile of this profile configuration
/// </summary> /// </summary>
public Guid ProfileId => Entity.ProfileId; public Guid ProfileId => Entity.Profile.Id;
#region Overrides of BreakableModel #region Overrides of BreakableModel
@ -265,8 +265,8 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
if (_disposed) if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration"); throw new ObjectDisposedException("ProfileConfiguration");
Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ModuleId); Module = enabledModules.FirstOrDefault(m => m.Id == Entity.ProfileConfiguration.ModuleId);
IsMissingModule = Module == null && Entity.ModuleId != null; IsMissingModule = Module == null && Entity.ProfileConfiguration.ModuleId != null;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -284,20 +284,20 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
if (_disposed) if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration"); throw new ObjectDisposedException("ProfileConfiguration");
Name = Entity.Name; Name = Entity.ProfileConfiguration.Name;
IsSuspended = Entity.IsSuspended; IsSuspended = Entity.ProfileConfiguration.IsSuspended;
ActivationBehaviour = (ActivationBehaviour) Entity.ActivationBehaviour; ActivationBehaviour = (ActivationBehaviour) Entity.ProfileConfiguration.ActivationBehaviour;
HotkeyMode = (ProfileConfigurationHotkeyMode) Entity.HotkeyMode; HotkeyMode = (ProfileConfigurationHotkeyMode) Entity.ProfileConfiguration.HotkeyMode;
FadeInAndOut = Entity.FadeInAndOut; FadeInAndOut = Entity.ProfileConfiguration.FadeInAndOut;
Order = Entity.Order; Order = Entity.ProfileConfiguration.Order;
Icon.Load(); Icon.Load();
if (Entity.ActivationCondition != null) if (Entity.ProfileConfiguration.ActivationCondition != null)
ActivationCondition.LoadFromEntity(Entity.ActivationCondition); ActivationCondition.LoadFromEntity(Entity.ProfileConfiguration.ActivationCondition);
EnableHotkey = Entity.EnableHotkey != null ? new Hotkey(Entity.EnableHotkey) : null; EnableHotkey = Entity.ProfileConfiguration.EnableHotkey != null ? new Hotkey(Entity.ProfileConfiguration.EnableHotkey) : null;
DisableHotkey = Entity.DisableHotkey != null ? new Hotkey(Entity.DisableHotkey) : null; DisableHotkey = Entity.ProfileConfiguration.DisableHotkey != null ? new Hotkey(Entity.ProfileConfiguration.DisableHotkey) : null;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -306,26 +306,26 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
if (_disposed) if (_disposed)
throw new ObjectDisposedException("ProfileConfiguration"); throw new ObjectDisposedException("ProfileConfiguration");
Entity.Name = Name; Entity.ProfileConfiguration.Name = Name;
Entity.IsSuspended = IsSuspended; Entity.ProfileConfiguration.IsSuspended = IsSuspended;
Entity.ActivationBehaviour = (int) ActivationBehaviour; Entity.ProfileConfiguration.ActivationBehaviour = (int) ActivationBehaviour;
Entity.HotkeyMode = (int) HotkeyMode; Entity.ProfileConfiguration.HotkeyMode = (int) HotkeyMode;
Entity.ProfileCategoryId = Category.Entity.Id; Entity.ProfileConfiguration.ProfileCategoryId = Category.Entity.Id;
Entity.FadeInAndOut = FadeInAndOut; Entity.ProfileConfiguration.FadeInAndOut = FadeInAndOut;
Entity.Order = Order; Entity.ProfileConfiguration.Order = Order;
Icon.Save(); Icon.Save();
ActivationCondition.Save(); ActivationCondition.Save();
Entity.ActivationCondition = ActivationCondition.Entity; Entity.ProfileConfiguration.ActivationCondition = ActivationCondition.Entity;
EnableHotkey?.Save(); EnableHotkey?.Save();
Entity.EnableHotkey = EnableHotkey?.Entity; Entity.ProfileConfiguration.EnableHotkey = EnableHotkey?.Entity;
DisableHotkey?.Save(); DisableHotkey?.Save();
Entity.DisableHotkey = DisableHotkey?.Entity; Entity.ProfileConfiguration.DisableHotkey = DisableHotkey?.Entity;
if (!IsMissingModule) if (!IsMissingModule)
Entity.ModuleId = Module?.Id; Entity.ProfileConfiguration.ModuleId = Module?.Id;
} }
#endregion #endregion

View File

@ -10,13 +10,13 @@ namespace Artemis.Core;
/// </summary> /// </summary>
public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
{ {
private readonly ProfileConfigurationEntity _entity; private readonly ProfileContainerEntity _entity;
private bool _fill; private bool _fill;
private string? _iconName; private string? _iconName;
private Stream? _iconStream; private byte[]? _iconBytes;
private ProfileConfigurationIconType _iconType; private ProfileConfigurationIconType _iconType;
internal ProfileConfigurationIcon(ProfileConfigurationEntity entity) internal ProfileConfigurationIcon(ProfileContainerEntity entity)
{ {
_entity = entity; _entity = entity;
} }
@ -48,6 +48,15 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
set => SetAndNotify(ref _fill, value); set => SetAndNotify(ref _fill, value);
} }
/// <summary>
/// Gets or sets the icon bytes if <see cref="IconType" /> is <see cref="ProfileConfigurationIconType.BitmapImage" />
/// </summary>
public byte[]? IconBytes
{
get => _iconBytes;
private set => SetAndNotify(ref _iconBytes, value);
}
/// <summary> /// <summary>
/// Updates the <see cref="IconName" /> to the provided value and changes the <see cref="IconType" /> is /// Updates the <see cref="IconName" /> to the provided value and changes the <see cref="IconType" /> is
/// <see cref="ProfileConfigurationIconType.MaterialIcon" /> /// <see cref="ProfileConfigurationIconType.MaterialIcon" />
@ -55,9 +64,9 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
/// <param name="iconName">The name of the icon</param> /// <param name="iconName">The name of the icon</param>
public void SetIconByName(string iconName) public void SetIconByName(string iconName)
{ {
if (iconName == null) throw new ArgumentNullException(nameof(iconName)); ArgumentNullException.ThrowIfNull(iconName);
_iconStream?.Dispose(); IconBytes = null;
IconName = iconName; IconName = iconName;
IconType = ProfileConfigurationIconType.MaterialIcon; IconType = ProfileConfigurationIconType.MaterialIcon;
@ -65,42 +74,27 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
} }
/// <summary> /// <summary>
/// Updates the stream returned by <see cref="GetIconStream" /> to the provided stream /// Updates the <see cref="IconBytes" /> to the provided value and changes the <see cref="IconType" /> is
/// </summary> /// </summary>
/// <param name="stream">The stream to copy</param> /// <param name="stream">The stream to copy</param>
public void SetIconByStream(Stream stream) public void SetIconByStream(Stream stream)
{ {
if (stream == null) throw new ArgumentNullException(nameof(stream)); ArgumentNullException.ThrowIfNull(stream);
_iconStream?.Dispose();
_iconStream = new MemoryStream();
if (stream.CanSeek) if (stream.CanSeek)
stream.Seek(0, SeekOrigin.Begin); 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; IconName = null;
IconType = ProfileConfigurationIconType.BitmapImage; IconType = ProfileConfigurationIconType.BitmapImage;
OnIconUpdated(); OnIconUpdated();
} }
/// <summary>
/// Creates a copy of the stream containing the icon
/// </summary>
/// <returns>A stream containing the icon</returns>
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;
}
/// <summary> /// <summary>
/// Occurs when the icon was updated /// Occurs when the icon was updated
/// </summary> /// </summary>
@ -119,21 +113,24 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
/// <inheritdoc /> /// <inheritdoc />
public void Load() public void Load()
{ {
IconType = (ProfileConfigurationIconType) _entity.IconType; IconType = (ProfileConfigurationIconType) _entity.ProfileConfiguration.IconType;
Fill = _entity.IconFill; Fill = _entity.ProfileConfiguration.IconFill;
if (IconType != ProfileConfigurationIconType.MaterialIcon) if (IconType != ProfileConfigurationIconType.MaterialIcon)
return; return;
IconName = _entity.MaterialIcon; IconName = _entity.ProfileConfiguration.MaterialIcon;
IconBytes = IconType == ProfileConfigurationIconType.BitmapImage ? _entity.Icon : null;
OnIconUpdated(); OnIconUpdated();
} }
/// <inheritdoc /> /// <inheritdoc />
public void Save() public void Save()
{ {
_entity.IconType = (int) IconType; _entity.ProfileConfiguration.IconType = (int) IconType;
_entity.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null; _entity.ProfileConfiguration.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null;
_entity.IconFill = Fill; _entity.ProfileConfiguration.IconFill = Fill;
_entity.Icon = IconBytes ?? Array.Empty<byte>();
} }
#endregion #endregion

View File

@ -19,13 +19,7 @@ public interface IPluginSetting
string Name { get; } string Name { get; }
/// <summary> /// <summary>
/// Determines whether the setting has been changed /// Gets or sets whether changes must automatically be saved
/// </summary>
bool HasChanged { get; }
/// <summary>
/// Gets or sets whether changes must automatically be saved
/// <para>Note: When set to <c>true</c> <see cref="HasChanged" /> is always <c>false</c></para>
/// </summary> /// </summary>
bool AutoSave { get; set; } bool AutoSave { get; set; }

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
@ -24,7 +23,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
Name = pluginSettingEntity.Name; Name = pluginSettingEntity.Name;
try try
{ {
_value = pluginSettingEntity.Value.Deserialize<T>(IPluginSetting.SerializerOptions) ?? default!; _value = CoreJson.Deserialize<T>(pluginSettingEntity.Value)!;
} }
catch (JsonException) catch (JsonException)
{ {
@ -77,7 +76,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
public string Name { get; } public string Name { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool HasChanged => !JsonNode.DeepEquals(JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions), _pluginSettingEntity.Value); public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value;
/// <inheritdoc /> /// <inheritdoc />
public bool AutoSave { get; set; } public bool AutoSave { get; set; }
@ -85,7 +84,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
/// <inheritdoc /> /// <inheritdoc />
public void RejectChanges() public void RejectChanges()
{ {
Value = _pluginSettingEntity.Value.Deserialize<T>(IPluginSetting.SerializerOptions) ?? default!; Value = CoreJson.Deserialize<T>(_pluginSettingEntity.Value);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -94,7 +93,7 @@ public class PluginSetting<T> : CorePropertyChanged, IPluginSetting
if (!HasChanged) if (!HasChanged)
return; return;
_pluginSettingEntity.Value = JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions) ?? new JsonObject(); _pluginSettingEntity.Value = CoreJson.Serialize(Value);
_pluginRepository.SaveChanges(); _pluginRepository.SaveChanges();
OnSettingSaved(); OnSettingSaved();
} }

View File

@ -29,7 +29,6 @@ internal class CoreService : ICoreService
// ReSharper disable UnusedParameter.Local // ReSharper disable UnusedParameter.Local
public CoreService(IContainer container, public CoreService(IContainer container,
ILogger logger, ILogger logger,
StorageMigrationService _1, // injected to ensure migration runs early
ISettingsService settingsService, ISettingsService settingsService,
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IProfileService profileService, IProfileService profileService,

View File

@ -202,7 +202,7 @@ internal class DeviceService : IDeviceService
_enabledDevices.Add(device); _enabledDevices.Add(device);
device.IsEnabled = true; device.IsEnabled = true;
device.Save(); device.Save();
_deviceRepository.Save(device.DeviceEntity); _deviceRepository.SaveChanges();
OnDeviceEnabled(new DeviceEventArgs(device)); OnDeviceEnabled(new DeviceEventArgs(device));
UpdateLeds(); UpdateLeds();
@ -217,7 +217,7 @@ internal class DeviceService : IDeviceService
_enabledDevices.Remove(device); _enabledDevices.Remove(device);
device.IsEnabled = false; device.IsEnabled = false;
device.Save(); device.Save();
_deviceRepository.Save(device.DeviceEntity); _deviceRepository.SaveChanges();
OnDeviceDisabled(new DeviceEventArgs(device)); OnDeviceDisabled(new DeviceEventArgs(device));
UpdateLeds(); UpdateLeds();
@ -227,7 +227,7 @@ internal class DeviceService : IDeviceService
public void SaveDevice(ArtemisDevice artemisDevice) public void SaveDevice(ArtemisDevice artemisDevice)
{ {
artemisDevice.Save(); artemisDevice.Save();
_deviceRepository.Save(artemisDevice.DeviceEntity); _deviceRepository.SaveChanges();
UpdateLeds(); UpdateLeds();
} }
@ -236,7 +236,7 @@ internal class DeviceService : IDeviceService
{ {
foreach (ArtemisDevice artemisDevice in _devices) foreach (ArtemisDevice artemisDevice in _devices)
artemisDevice.Save(); artemisDevice.Save();
_deviceRepository.Save(_devices.Select(d => d.DeviceEntity)); _deviceRepository.SaveChanges();
UpdateLeds(); UpdateLeds();
} }
@ -254,6 +254,8 @@ internal class DeviceService : IDeviceService
{ {
_logger.Information("No device config found for {DeviceInfo}, device hash: {DeviceHashCode}. Adding a new entry", rgbDevice.DeviceInfo, deviceIdentifier); _logger.Information("No device config found for {DeviceInfo}, device hash: {DeviceHashCode}. Adding a new entry", rgbDevice.DeviceInfo, deviceIdentifier);
device = new ArtemisDevice(rgbDevice, deviceProvider); device = new ArtemisDevice(rgbDevice, deviceProvider);
_deviceRepository.Add(device.DeviceEntity);
_deviceRepository.SaveChanges();
} }
LoadDeviceLayout(device); LoadDeviceLayout(device);

View File

@ -807,7 +807,7 @@ internal class PluginManagementService : IPluginManagementService
plugin.Entity.Features.Add(featureInfo.Instance!.Entity); plugin.Entity.Features.Add(featureInfo.Instance!.Entity);
} }
_pluginRepository.SavePlugin(plugin.Entity); _pluginRepository.SaveChanges();
} }
#endregion #endregion

View File

@ -12,7 +12,7 @@ internal class ScriptingService : IScriptingService
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
private readonly List<ScriptingProvider> _scriptingProviders; private readonly List<ScriptingProvider> _scriptingProviders;
public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService) public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService)
{ {
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
@ -29,10 +29,13 @@ internal class ScriptingService : IScriptingService
// No need to sub to Deactivated, scripts will deactivate themselves // No need to sub to Deactivated, scripts will deactivate themselves
profileService.ProfileActivated += ProfileServiceOnProfileActivated; profileService.ProfileActivated += ProfileServiceOnProfileActivated;
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations) foreach (ProfileCategory profileCategory in _profileService.ProfileCategories)
{ {
if (profileConfiguration.Profile != null) foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
InitializeProfileScripts(profileConfiguration.Profile); {
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
} }
} }
@ -112,11 +115,14 @@ internal class ScriptingService : IScriptingService
{ {
_scriptingProviders.Clear(); _scriptingProviders.Clear();
_scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType<ScriptingProvider>()); _scriptingProviders.AddRange(_pluginManagementService.GetFeaturesOfType<ScriptingProvider>());
foreach (ProfileConfiguration profileConfiguration in _profileService.ProfileConfigurations) foreach (ProfileCategory profileCategory in _profileService.ProfileCategories)
{ {
if (profileConfiguration.Profile != null) foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
InitializeProfileScripts(profileConfiguration.Profile); {
if (profileConfiguration.Profile != null)
InitializeProfileScripts(profileConfiguration.Profile);
}
} }
} }

View File

@ -16,11 +16,6 @@ public interface IProfileService : IArtemisService
/// </summary> /// </summary>
ReadOnlyCollection<ProfileCategory> ProfileCategories { get; } ReadOnlyCollection<ProfileCategory> ProfileCategories { get; }
/// <summary>
/// Gets a read only collection containing all the profile configurations.
/// </summary>
ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
/// <summary> /// <summary>
/// Gets or sets the focused profile configuration which is rendered exclusively. /// Gets or sets the focused profile configuration which is rendered exclusively.
/// </summary> /// </summary>
@ -101,16 +96,6 @@ public interface IProfileService : IArtemisService
/// <param name="profileConfiguration"></param> /// <param name="profileConfiguration"></param>
void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration); void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration);
/// <summary>
/// Loads the icon of this profile configuration if needed and puts it into <c>ProfileConfiguration.Icon.FileIcon</c>.
/// </summary>
void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary>
/// Saves the current icon of this profile.
/// </summary>
void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration);
/// <summary> /// <summary>
/// Writes the profile to persistent storage. /// Writes the profile to persistent storage.
/// </summary> /// </summary>

View File

@ -25,7 +25,6 @@ internal class ProfileService : IProfileService
private readonly IDeviceService _deviceService; private readonly IDeviceService _deviceService;
private readonly List<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new(); private readonly List<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new();
private readonly List<ProfileCategory> _profileCategories; private readonly List<ProfileCategory> _profileCategories;
private readonly IProfileRepository _profileRepository;
private readonly List<IProfileMigration> _profileMigrators; private readonly List<IProfileMigration> _profileMigrators;
private readonly List<Exception> _renderExceptions = new(); private readonly List<Exception> _renderExceptions = new();
private readonly List<Exception> _updateExceptions = new(); private readonly List<Exception> _updateExceptions = new();
@ -38,17 +37,17 @@ internal class ProfileService : IProfileService
IPluginManagementService pluginManagementService, IPluginManagementService pluginManagementService,
IInputService inputService, IInputService inputService,
IDeviceService deviceService, IDeviceService deviceService,
IProfileRepository profileRepository,
List<IProfileMigration> profileMigrators) List<IProfileMigration> profileMigrators)
{ {
_logger = logger; _logger = logger;
_profileCategoryRepository = profileCategoryRepository; _profileCategoryRepository = profileCategoryRepository;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_deviceService = deviceService; _deviceService = deviceService;
_profileRepository = profileRepository;
_profileMigrators = profileMigrators; _profileMigrators = profileMigrators;
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order)); _profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
ProfileCategories = new ReadOnlyCollection<ProfileCategory>(_profileCategories);
_deviceService.LedsChanged += DeviceServiceOnLedsChanged; _deviceService.LedsChanged += DeviceServiceOnLedsChanged;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
@ -166,50 +165,7 @@ internal class ProfileService : IProfileService
} }
/// <inheritdoc /> /// <inheritdoc />
public ReadOnlyCollection<ProfileCategory> ProfileCategories public ReadOnlyCollection<ProfileCategory> ProfileCategories { get; }
{
get
{
lock (_profileRepository)
{
return _profileCategories.AsReadOnly();
}
}
}
/// <inheritdoc />
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations
{
get
{
lock (_profileRepository)
{
return _profileCategories.SelectMany(c => c.ProfileConfigurations).ToList().AsReadOnly();
}
}
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
}
/// <inheritdoc /> /// <inheritdoc />
public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration) public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration)
@ -226,21 +182,7 @@ internal class ProfileService : IProfileService
return profileConfiguration.Profile; return profileConfiguration.Profile;
} }
ProfileEntity? profileEntity; Profile profile = new(profileConfiguration, profileConfiguration.Entity.Profile);
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.PopulateLeds(_deviceService.EnabledDevices); profile.PopulateLeds(_deviceService.EnabledDevices);
if (profile.IsFreshImport) if (profile.IsFreshImport)
@ -285,39 +227,34 @@ internal class ProfileService : IProfileService
{ {
DeactivateProfile(profileConfiguration); DeactivateProfile(profileConfiguration);
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); ProfileCategory category = profileConfiguration.Category;
if (profileEntity == null)
return;
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); category.RemoveProfileConfiguration(profileConfiguration);
_profileRepository.Remove(profileEntity); category.Entity.ProfileConfigurations.Remove(profileConfiguration.Entity);
SaveProfileCategory(profileConfiguration.Category);
_profileCategoryRepository.SaveChanges();
} }
/// <inheritdoc /> /// <inheritdoc />
public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) public ProfileCategory CreateProfileCategory(string name, bool addToTop = false)
{ {
ProfileCategory profileCategory; ProfileCategory profileCategory;
lock (_profileRepository) if (addToTop)
{ {
if (addToTop) profileCategory = new ProfileCategory(name, 1);
foreach (ProfileCategory category in _profileCategories)
{ {
profileCategory = new ProfileCategory(name, 1); category.Order++;
foreach (ProfileCategory category in _profileCategories) category.Save();
{
category.Order++;
category.Save();
_profileCategoryRepository.Save(category.Entity);
}
} }
else
{
profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
}
_profileCategories.Add(profileCategory);
SaveProfileCategory(profileCategory);
} }
else
{
profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
}
_profileCategoryRepository.Add(profileCategory.Entity);
_profileCategories.Add(profileCategory);
OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory)); OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory));
return profileCategory; return profileCategory;
@ -326,15 +263,11 @@ internal class ProfileService : IProfileService
/// <inheritdoc /> /// <inheritdoc />
public void DeleteProfileCategory(ProfileCategory profileCategory) public void DeleteProfileCategory(ProfileCategory profileCategory)
{ {
List<ProfileConfiguration> profileConfigurations = profileCategory.ProfileConfigurations.ToList(); foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations.ToList())
foreach (ProfileConfiguration profileConfiguration in profileConfigurations)
RemoveProfileConfiguration(profileConfiguration); RemoveProfileConfiguration(profileConfiguration);
lock (_profileRepository) _profileCategories.Remove(profileCategory);
{ _profileCategoryRepository.Remove(profileCategory.Entity);
_profileCategories.Remove(profileCategory);
_profileCategoryRepository.Remove(profileCategory.Entity);
}
OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory)); OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory));
} }
@ -343,24 +276,20 @@ internal class ProfileService : IProfileService
public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon) public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon)
{ {
ProfileConfiguration configuration = new(category, name, 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, 0);
SaveProfileCategory(category);
return configuration; return configuration;
} }
/// <inheritdoc /> /// <inheritdoc />
public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration) public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration)
{ {
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); ProfileCategory category = profileConfiguration.Category;
category.RemoveProfileConfiguration(profileConfiguration);
DeactivateProfile(profileConfiguration); DeactivateProfile(profileConfiguration);
SaveProfileCategory(profileConfiguration.Category); SaveProfileCategory(profileConfiguration.Category);
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
if (profileEntity != null)
_profileRepository.Remove(profileEntity);
profileConfiguration.Dispose(); profileConfiguration.Dispose();
} }
@ -369,7 +298,7 @@ internal class ProfileService : IProfileService
public void SaveProfileCategory(ProfileCategory profileCategory) public void SaveProfileCategory(ProfileCategory profileCategory)
{ {
profileCategory.Save(); profileCategory.Save();
_profileCategoryRepository.Save(profileCategory.Entity); _profileCategoryRepository.SaveChanges();
lock (_profileCategories) lock (_profileCategories)
{ {
@ -392,11 +321,13 @@ internal class ProfileService : IProfileService
profile.IsFreshImport = false; profile.IsFreshImport = false;
profile.ProfileEntity.IsFreshImport = false; profile.ProfileEntity.IsFreshImport = false;
_profileRepository.Save(profile.ProfileEntity); SaveProfileCategory(profile.Configuration.Category);
// If the provided profile is external (cloned or from the workshop?) but it is loaded locally too, reload the local instance // 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 // 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) if (localInstance == null)
return; return;
DeactivateProfile(localInstance); DeactivateProfile(localInstance);
@ -406,12 +337,8 @@ internal class ProfileService : IProfileService
/// <inheritdoc /> /// <inheritdoc />
public async Task<Stream> ExportProfile(ProfileConfiguration profileConfiguration) public async Task<Stream> ExportProfile(ProfileConfiguration profileConfiguration)
{ {
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); string configurationJson = CoreJson.Serialize(profileConfiguration.Entity.ProfileConfiguration);
if (profileEntity == null) string profileJson = CoreJson.Serialize(profileConfiguration.Entity.Profile);
throw new ArtemisCoreException("Could not locate profile entity");
string configurationJson = CoreJson.Serialize(profileConfiguration.Entity);
string profileJson = CoreJson.Serialize(profileEntity);
MemoryStream archiveStream = new(); MemoryStream archiveStream = new();
@ -430,12 +357,11 @@ internal class ProfileService : IProfileService
await entryStream.WriteAsync(Encoding.Default.GetBytes(profileJson)); await entryStream.WriteAsync(Encoding.Default.GetBytes(profileJson));
} }
await using Stream? iconStream = profileConfiguration.Icon.GetIconStream(); if (profileConfiguration.Icon.IconBytes != null)
if (iconStream != null)
{ {
ZipArchiveEntry iconEntry = archive.CreateEntry("icon.png"); ZipArchiveEntry iconEntry = archive.CreateEntry("icon.png");
await using Stream entryStream = iconEntry.Open(); await using Stream entryStream = iconEntry.Open();
await iconStream.CopyToAsync(entryStream); await entryStream.WriteAsync(profileConfiguration.Icon.IconBytes);
} }
} }
@ -495,26 +421,26 @@ internal class ProfileService : IProfileService
if (markAsFreshImport) if (markAsFreshImport)
profileEntity.IsFreshImport = true; profileEntity.IsFreshImport = true;
if (_profileRepository.Get(profileEntity.Id) == null) if (makeUnique && ProfileCategories.SelectMany(c => c.ProfileConfigurations).Any(c => c.ProfileId == profileEntity.Id))
_profileRepository.Add(profileEntity);
else
throw new ArtemisCoreException($"Cannot import this profile without {nameof(makeUnique)} being true"); throw new ArtemisCoreException($"Cannot import this profile without {nameof(makeUnique)} being true");
// A new GUID will be given on save ProfileContainerEntity containerEntity = new() {ProfileConfiguration = configurationEntity, Profile = profileEntity};
configurationEntity.FileIconId = Guid.Empty;
ProfileConfiguration profileConfiguration = new(category, configurationEntity);
if (nameAffix != null)
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
// If an icon was provided, import that as well // If an icon was provided, import that as well
if (iconEntry != null) if (iconEntry != null)
{ {
await using Stream iconStream = iconEntry.Open(); await using Stream iconStream = iconEntry.Open();
profileConfiguration.Icon.SetIconByStream(iconStream); using MemoryStream ms = new();
SaveProfileConfigurationIcon(profileConfiguration); await iconStream.CopyToAsync(ms);
containerEntity.Icon = ms.ToArray();
} }
profileConfiguration.Entity.ProfileId = profileEntity.Id; // A new GUID will be given on save
configurationEntity.FileIconId = Guid.Empty;
ProfileConfiguration profileConfiguration = new(category, containerEntity);
if (nameAffix != null)
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id;
category.AddProfileConfiguration(profileConfiguration, targetIndex); category.AddProfileConfiguration(profileConfiguration, targetIndex);
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>(); List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
@ -548,7 +474,7 @@ internal class ProfileService : IProfileService
renderProfileElement.Save(); renderProfileElement.Save();
_logger.Debug("Adapt profile - Saving " + profile); _logger.Debug("Adapt profile - Saving " + profile);
_profileRepository.Save(profile.ProfileEntity); SaveProfileCategory(profile.Configuration.Category);
} }
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e) private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
@ -565,7 +491,7 @@ internal class ProfileService : IProfileService
return; return;
configurationJson["Version"] ??= 0; configurationJson["Version"] ??= 0;
foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version)) foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version))
{ {
if (profileMigrator.Version <= configurationJson["Version"]!.GetValue<int>()) if (profileMigrator.Version <= configurationJson["Version"]!.GetValue<int>())
@ -581,27 +507,27 @@ internal class ProfileService : IProfileService
/// </summary> /// </summary>
private void ActiveProfilesPopulateLeds() private void ActiveProfilesPopulateLeds()
{ {
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) foreach (ProfileCategory profileCategory in ProfileCategories)
{ {
if (profileConfiguration.Profile == null) continue; foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices); {
if (profileConfiguration.Profile == null) continue;
profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices);
if (!profileConfiguration.Profile.IsFreshImport) continue; if (!profileConfiguration.Profile.IsFreshImport) continue;
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile); _logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile);
AdaptProfile(profileConfiguration.Profile); AdaptProfile(profileConfiguration.Profile);
}
} }
} }
private void UpdateModules() private void UpdateModules()
{ {
lock (_profileRepository) List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
foreach (ProfileCategory profileCategory in ProfileCategories)
{ {
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>(); foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
foreach (ProfileCategory profileCategory in _profileCategories) profileConfiguration.LoadModules(modules);
{
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
profileConfiguration.LoadModules(modules);
}
} }
} }

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Artemis.Core\Artemis.Core.csproj" />
<ProjectReference Include="..\Artemis.Storage\Artemis.Storage.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,21 @@
using Artemis.Core.DryIoc;
using DryIoc;
using Microsoft.EntityFrameworkCore;
namespace Artemis.Storage.Migrator;
class Program
{
static void Main(string[] args)
{
Container container = new Container(rules => rules
.WithMicrosoftDependencyInjectionRules()
.WithConcreteTypeDynamicRegistrations()
.WithoutThrowOnRegisteringDisposableTransient());
container.RegisterCore();
container.Resolve<ArtemisDbContext>().Database.EnsureCreated();
}
}

Binary file not shown.

View File

@ -1,3 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Artemis.Storage.Entities.General; using Artemis.Storage.Entities.General;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
@ -14,24 +18,40 @@ public class ArtemisDbContext : DbContext
public DbSet<PluginEntity> Plugins => Set<PluginEntity>(); public DbSet<PluginEntity> Plugins => Set<PluginEntity>();
public DbSet<PluginSettingEntity> PluginSettings => Set<PluginSettingEntity>(); public DbSet<PluginSettingEntity> PluginSettings => Set<PluginSettingEntity>();
public DbSet<ProfileCategoryEntity> ProfileCategories => Set<ProfileCategoryEntity>(); public DbSet<ProfileCategoryEntity> ProfileCategories => Set<ProfileCategoryEntity>();
public DbSet<ProfileContainerEntity> Profiles => Set<ProfileContainerEntity>();
public DbSet<ReleaseEntity> Releases => Set<ReleaseEntity>(); public DbSet<ReleaseEntity> Releases => Set<ReleaseEntity>();
public string DataFolder { get; set; } = string.Empty;
public JsonSerializerOptions JsonSerializerOptions { get; set; } = JsonSerializerOptions.Default;
/// <inheritdoc />
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite($"Data Source={Path.Combine(DataFolder, "artemis.db")}");
}
/// <inheritdoc /> /// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<DeviceEntity>() modelBuilder.Entity<DeviceEntity>()
.OwnsOne(d => d.InputIdentifiers, builder => builder.ToJson()) .OwnsOne(d => d.InputIdentifiers, builder => builder.ToJson())
.OwnsOne(d => d.InputMappings, builder => builder.ToJson()); .OwnsOne(d => d.InputMappings, builder => builder.ToJson());
modelBuilder.Entity<EntryEntity>()
.OwnsOne(e => e.Metadata, builder => builder.ToJson());
modelBuilder.Entity<PluginSettingEntity>() modelBuilder.Entity<EntryEntity>()
.OwnsOne(s => s.Value, builder => builder.ToJson()); .Property(e => e.Metadata)
.HasConversion(
v => JsonSerializer.Serialize(v, JsonSerializerOptions),
v => JsonSerializer.Deserialize<Dictionary<string, object>>(v, JsonSerializerOptions) ?? new Dictionary<string, object>());
modelBuilder.Entity<ProfileContainerEntity>() modelBuilder.Entity<ProfileContainerEntity>()
.OwnsOne(c => c.ProfileConfiguration, builder => builder.ToJson()) .Property(e => e.ProfileConfiguration)
.OwnsOne(c => c.Profile, builder => builder.ToJson()); .HasConversion(
v => JsonSerializer.Serialize(v, JsonSerializerOptions),
v => JsonSerializer.Deserialize<ProfileConfigurationEntity>(v, JsonSerializerOptions) ?? new ProfileConfigurationEntity());
modelBuilder.Entity<ProfileContainerEntity>()
.Property(e => e.Profile)
.HasConversion(
v => JsonSerializer.Serialize(v, JsonSerializerOptions),
v => JsonSerializer.Deserialize<ProfileEntity>(v, JsonSerializerOptions) ?? new ProfileEntity());
} }
} }

View File

@ -24,6 +24,8 @@ public class PluginEntity
/// </summary> /// </summary>
public class PluginFeatureEntity public class PluginFeatureEntity
{ {
public Guid Id { get; set; }
public string Type { get; set; } = string.Empty; public string Type { get; set; } = string.Empty;
public bool IsEnabled { get; set; } public bool IsEnabled { get; set; }
} }

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Text.Json.Nodes;
namespace Artemis.Storage.Entities.Plugins; namespace Artemis.Storage.Entities.Plugins;
@ -12,5 +11,5 @@ public class PluginSettingEntity
public Guid PluginGuid { get; set; } public Guid PluginGuid { get; set; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public JsonNode Value { get; set; } = new JsonObject(); public string Value { get; set; } = string.Empty;
} }

View File

@ -13,11 +13,4 @@ public class ProfileCategoryEntity
public int Order { get; set; } public int Order { get; set; }
public List<ProfileContainerEntity> ProfileConfigurations { get; set; } = new(); public List<ProfileContainerEntity> ProfileConfigurations { get; set; } = new();
}
public class ProfileContainerEntity
{
public byte[] Icon { get; set; } = Array.Empty<byte>();
public ProfileConfigurationEntity ProfileConfiguration { get; set; } = new();
public ProfileEntity Profile { get; set; } = new();
} }

View File

@ -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<byte>();
public ProfileCategoryEntity ProfileCategory { get; set; } = null!;
public ProfileConfigurationEntity ProfileConfiguration { get; set; } = new();
public ProfileEntity Profile { get; set; } = new();
}

View File

@ -18,5 +18,5 @@ public class EntryEntity
public string ReleaseVersion { get; set; } = string.Empty; public string ReleaseVersion { get; set; } = string.Empty;
public DateTimeOffset InstalledAt { get; set; } public DateTimeOffset InstalledAt { get; set; }
public Dictionary<string,JsonNode>? Metadata { get; set; } public Dictionary<string, object> Metadata { get; set; }
} }

View File

@ -0,0 +1,323 @@
// <auto-generated />
using System;
using Artemis.Storage;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Artemis.Storage.Migrations
{
[DbContext(typeof(ArtemisDbContext))]
[Migration("20240308203921_Initial")]
partial class Initial
{
/// <inheritdoc />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("InstalledAt")
.HasColumnType("TEXT");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Releases");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Plugins");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<Guid?>("PluginEntityId")
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("PluginEntityId");
b.ToTable("PluginFeatureEntity");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("PluginGuid")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PluginSettings");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsCollapsed")
.HasColumnType("INTEGER");
b.Property<bool>("IsSuspended")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("ProfileCategories");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<byte[]>("Icon")
.IsRequired()
.HasColumnType("BLOB");
b.Property<string>("Profile")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("ProfileCategoryId")
.HasColumnType("TEXT");
b.Property<string>("ProfileConfiguration")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ProfileCategoryId");
b.ToTable("ProfileContainerEntity");
});
modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<float>("BlueScale")
.HasColumnType("REAL");
b.Property<string>("Categories")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DeviceProvider")
.IsRequired()
.HasColumnType("TEXT");
b.Property<float>("GreenScale")
.HasColumnType("REAL");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("LayoutParameter")
.HasColumnType("TEXT");
b.Property<string>("LayoutType")
.HasColumnType("TEXT");
b.Property<string>("LogicalLayout")
.HasColumnType("TEXT");
b.Property<int>("PhysicalLayout")
.HasColumnType("INTEGER");
b.Property<float>("RedScale")
.HasColumnType("REAL");
b.Property<float>("Rotation")
.HasColumnType("REAL");
b.Property<float>("Scale")
.HasColumnType("REAL");
b.Property<float>("X")
.HasColumnType("REAL");
b.Property<float>("Y")
.HasColumnType("REAL");
b.Property<int>("ZIndex")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Devices");
});
modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("EntryId")
.HasColumnType("INTEGER");
b.Property<int>("EntryType")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("InstalledAt")
.HasColumnType("TEXT");
b.Property<string>("Metadata")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("ReleaseId")
.HasColumnType("INTEGER");
b.Property<string>("ReleaseVersion")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Entries");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b =>
{
b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null)
.WithMany("Features")
.HasForeignKey("PluginEntityId");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b =>
{
b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory")
.WithMany("ProfileConfigurations")
.HasForeignKey("ProfileCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ProfileCategory");
});
modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
{
b.OwnsOne("System.Collections.Generic.List<Artemis.Storage.Entities.Surface.DeviceInputIdentifierEntity>", "InputIdentifiers", b1 =>
{
b1.Property<string>("DeviceEntityId")
.HasColumnType("TEXT");
b1.Property<int>("Capacity")
.HasColumnType("INTEGER");
b1.HasKey("DeviceEntityId");
b1.ToTable("Devices");
b1.ToJson("InputIdentifiers");
b1.WithOwner()
.HasForeignKey("DeviceEntityId");
});
b.OwnsOne("System.Collections.Generic.List<Artemis.Storage.Entities.Surface.InputMappingEntity>", "InputMappings", b1 =>
{
b1.Property<string>("DeviceEntityId")
.HasColumnType("TEXT");
b1.Property<int>("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
}
}
}

View File

@ -0,0 +1,194 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Artemis.Storage.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Devices",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
DeviceProvider = table.Column<string>(type: "TEXT", nullable: false),
X = table.Column<float>(type: "REAL", nullable: false),
Y = table.Column<float>(type: "REAL", nullable: false),
Rotation = table.Column<float>(type: "REAL", nullable: false),
Scale = table.Column<float>(type: "REAL", nullable: false),
ZIndex = table.Column<int>(type: "INTEGER", nullable: false),
RedScale = table.Column<float>(type: "REAL", nullable: false),
GreenScale = table.Column<float>(type: "REAL", nullable: false),
BlueScale = table.Column<float>(type: "REAL", nullable: false),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
PhysicalLayout = table.Column<int>(type: "INTEGER", nullable: false),
LogicalLayout = table.Column<string>(type: "TEXT", nullable: true),
LayoutType = table.Column<string>(type: "TEXT", nullable: true),
LayoutParameter = table.Column<string>(type: "TEXT", nullable: true),
Categories = table.Column<string>(type: "TEXT", nullable: false),
InputIdentifiers = table.Column<string>(type: "TEXT", nullable: false),
InputMappings = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Devices", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Entries",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
EntryId = table.Column<long>(type: "INTEGER", nullable: false),
EntryType = table.Column<int>(type: "INTEGER", nullable: false),
Author = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
ReleaseId = table.Column<long>(type: "INTEGER", nullable: false),
ReleaseVersion = table.Column<string>(type: "TEXT", nullable: false),
InstalledAt = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
Metadata = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Entries", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Plugins",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Plugins", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PluginSettings",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
PluginGuid = table.Column<Guid>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
Value = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PluginSettings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ProfileCategories",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
IsCollapsed = table.Column<bool>(type: "INTEGER", nullable: false),
IsSuspended = table.Column<bool>(type: "INTEGER", nullable: false),
Order = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProfileCategories", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Releases",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Version = table.Column<string>(type: "TEXT", nullable: false),
InstalledAt = table.Column<DateTimeOffset>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Releases", x => x.Id);
});
migrationBuilder.CreateTable(
name: "PluginFeatureEntity",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: false),
IsEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
PluginEntityId = table.Column<Guid>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PluginFeatureEntity", x => x.Id);
table.ForeignKey(
name: "FK_PluginFeatureEntity_Plugins_PluginEntityId",
column: x => x.PluginEntityId,
principalTable: "Plugins",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "ProfileContainerEntity",
columns: table => new
{
Id = table.Column<Guid>(type: "TEXT", nullable: false),
Icon = table.Column<byte[]>(type: "BLOB", nullable: false),
ProfileCategoryId = table.Column<Guid>(type: "TEXT", nullable: false),
ProfileConfiguration = table.Column<string>(type: "TEXT", nullable: false),
Profile = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ProfileContainerEntity", x => x.Id);
table.ForeignKey(
name: "FK_ProfileContainerEntity_ProfileCategories_ProfileCategoryId",
column: x => x.ProfileCategoryId,
principalTable: "ProfileCategories",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_PluginFeatureEntity_PluginEntityId",
table: "PluginFeatureEntity",
column: "PluginEntityId");
migrationBuilder.CreateIndex(
name: "IX_ProfileContainerEntity_ProfileCategoryId",
table: "ProfileContainerEntity",
column: "ProfileCategoryId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Devices");
migrationBuilder.DropTable(
name: "Entries");
migrationBuilder.DropTable(
name: "PluginFeatureEntity");
migrationBuilder.DropTable(
name: "PluginSettings");
migrationBuilder.DropTable(
name: "ProfileContainerEntity");
migrationBuilder.DropTable(
name: "Releases");
migrationBuilder.DropTable(
name: "Plugins");
migrationBuilder.DropTable(
name: "ProfileCategories");
}
}
}

View File

@ -0,0 +1,320 @@
// <auto-generated />
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<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("InstalledAt")
.HasColumnType("TEXT");
b.Property<string>("Version")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Releases");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Plugins");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<Guid?>("PluginEntityId")
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("PluginEntityId");
b.ToTable("PluginFeatureEntity");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("PluginGuid")
.HasColumnType("TEXT");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PluginSettings");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsCollapsed")
.HasColumnType("INTEGER");
b.Property<bool>("IsSuspended")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Order")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("ProfileCategories");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<byte[]>("Icon")
.IsRequired()
.HasColumnType("BLOB");
b.Property<string>("Profile")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("ProfileCategoryId")
.HasColumnType("TEXT");
b.Property<string>("ProfileConfiguration")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ProfileCategoryId");
b.ToTable("ProfileContainerEntity");
});
modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<float>("BlueScale")
.HasColumnType("REAL");
b.Property<string>("Categories")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DeviceProvider")
.IsRequired()
.HasColumnType("TEXT");
b.Property<float>("GreenScale")
.HasColumnType("REAL");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("LayoutParameter")
.HasColumnType("TEXT");
b.Property<string>("LayoutType")
.HasColumnType("TEXT");
b.Property<string>("LogicalLayout")
.HasColumnType("TEXT");
b.Property<int>("PhysicalLayout")
.HasColumnType("INTEGER");
b.Property<float>("RedScale")
.HasColumnType("REAL");
b.Property<float>("Rotation")
.HasColumnType("REAL");
b.Property<float>("Scale")
.HasColumnType("REAL");
b.Property<float>("X")
.HasColumnType("REAL");
b.Property<float>("Y")
.HasColumnType("REAL");
b.Property<int>("ZIndex")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Devices");
});
modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Author")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("EntryId")
.HasColumnType("INTEGER");
b.Property<int>("EntryType")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("InstalledAt")
.HasColumnType("TEXT");
b.Property<string>("Metadata")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<long>("ReleaseId")
.HasColumnType("INTEGER");
b.Property<string>("ReleaseVersion")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Entries");
});
modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b =>
{
b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null)
.WithMany("Features")
.HasForeignKey("PluginEntityId");
});
modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b =>
{
b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory")
.WithMany("ProfileConfigurations")
.HasForeignKey("ProfileCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ProfileCategory");
});
modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b =>
{
b.OwnsOne("System.Collections.Generic.List<Artemis.Storage.Entities.Surface.DeviceInputIdentifierEntity>", "InputIdentifiers", b1 =>
{
b1.Property<string>("DeviceEntityId")
.HasColumnType("TEXT");
b1.Property<int>("Capacity")
.HasColumnType("INTEGER");
b1.HasKey("DeviceEntityId");
b1.ToTable("Devices");
b1.ToJson("InputIdentifiers");
b1.WithOwner()
.HasForeignKey("DeviceEntityId");
});
b.OwnsOne("System.Collections.Generic.List<Artemis.Storage.Entities.Surface.InputMappingEntity>", "InputMappings", b1 =>
{
b1.Property<string>("DeviceEntityId")
.HasColumnType("TEXT");
b1.Property<int>("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
}
}
}

View File

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using Artemis.Storage.Entities.Profile;
namespace Artemis.Storage.Repositories.Interfaces;
public interface IProfileRepository : IRepository
{
void Add(ProfileContainerEntity profileContainerEntity);
void Remove(ProfileContainerEntity profileContainerEntity);
List<ProfileContainerEntity> GetAll();
ProfileContainerEntity? Get(Guid id);
void SaveChanges();
}

View File

@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Repositories.Interfaces;
using LiteDB;
namespace Artemis.Storage.Repositories;
internal class ProfileRepository : IProfileRepository
{
private readonly ArtemisDbContext _dbContext;
public ProfileRepository(ArtemisDbContext dbContext)
{
_dbContext = dbContext;
}
public void Add(ProfileContainerEntity profileContainerEntity)
{
_dbContext.Profiles.Add(profileContainerEntity);
SaveChanges();
}
public void Remove(ProfileContainerEntity profileContainerEntity)
{
_dbContext.Profiles.Remove(profileContainerEntity);
SaveChanges();
}
public List<ProfileContainerEntity> GetAll()
{
return _dbContext.Profiles.ToList();
}
public ProfileContainerEntity? Get(Guid id)
{
return _dbContext.Profiles.FirstOrDefault(c => c.Profile.Id == id);
}
public void SaveChanges()
{
_dbContext.SaveChanges();
}
}

View File

@ -59,4 +59,12 @@ public static class StorageManager
throw new Exception($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e); throw new Exception($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e);
} }
} }
public static ArtemisDbContext CreateDbContext(string dataFolder)
{
return new ArtemisDbContext()
{
DataFolder = dataFolder
};
}
} }

View File

@ -46,17 +46,10 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable
? new MaterialIcon {Kind = parsedIcon!} ? new MaterialIcon {Kind = parsedIcon!}
: new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; : new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
} }
else if (ConfigurationIcon.IconBytes != null)
Dispatcher.UIThread.Post(() => LoadFromBitmap(ConfigurationIcon, new MemoryStream(ConfigurationIcon.IconBytes)), DispatcherPriority.ApplicationIdle);
else else
{ Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
Dispatcher.UIThread.Post(() =>
{
Stream? stream = ConfigurationIcon?.GetIconStream();
if (stream == null || ConfigurationIcon == null)
Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark};
else
LoadFromBitmap(ConfigurationIcon, stream);
}, DispatcherPriority.ApplicationIdle);
}
} }
catch (Exception) catch (Exception)
{ {

View File

@ -186,7 +186,7 @@ public partial class ProfileEditorViewModel : RoutableScreen<ProfileEditorViewMo
/// <inheritdoc /> /// <inheritdoc />
public override async Task OnNavigating(ProfileEditorViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken) public override async Task OnNavigating(ProfileEditorViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
{ {
ProfileConfiguration? profileConfiguration = _profileService.ProfileConfigurations.FirstOrDefault(c => c.ProfileId == parameters.ProfileId); ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId);
// If the profile doesn't exist, cancel navigation // If the profile doesn't exist, cancel navigation
if (profileConfiguration == null) if (profileConfiguration == null)

View File

@ -123,7 +123,6 @@ public partial class ProfileConfigurationEditViewModel : DialogViewModelBase<Pro
await SaveIcon(); await SaveIcon();
_profileService.SaveProfileConfigurationIcon(ProfileConfiguration);
_profileService.SaveProfileCategory(_profileCategory); _profileService.SaveProfileCategory(_profileCategory);
Close(ProfileConfiguration); Close(ProfileConfiguration);
} }
@ -144,7 +143,7 @@ public partial class ProfileConfigurationEditViewModel : DialogViewModelBase<Pro
{ {
try try
{ {
Stream? iconStream = _profileConfiguration.Icon.GetIconStream(); Stream? iconStream = _profileConfiguration.Icon.IconBytes != null ? new MemoryStream(_profileConfiguration.Icon.IconBytes) : null;
SelectedBitmapSource = iconStream != null ? new Bitmap(iconStream) : null; SelectedBitmapSource = iconStream != null ? new Bitmap(iconStream) : null;
} }
catch (Exception e) catch (Exception e)

View File

@ -40,15 +40,6 @@ public class SidebarProfileConfigurationViewModel : ActivatableViewModelBase
.Select(p => p == null) .Select(p => p == null)
.ToProperty(this, vm => vm.IsDisabled) .ToProperty(this, vm => vm.IsDisabled)
.DisposeWith(d)); .DisposeWith(d));
try
{
_profileService.LoadProfileConfigurationIcon(ProfileConfiguration);
}
catch (Exception)
{
// ignored, too bad but don't crash over corrupt icons
}
} }
public ProfileConfiguration ProfileConfiguration { get; } public ProfileConfiguration ProfileConfiguration { get; }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
@ -27,10 +28,7 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel
_profileService = profileService; _profileService = profileService;
// Use copies of the profiles, the originals are used by the core and could be disposed // Use copies of the profiles, the originals are used by the core and could be disposed
Profiles = new ObservableCollection<ProfileConfiguration>(_profileService.ProfileConfigurations.Select(_profileService.CloneProfileConfiguration)); Profiles = new ObservableCollection<ProfileConfiguration>(_profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).Select(_profileService.CloneProfileConfiguration));
foreach (ProfileConfiguration profileConfiguration in Profiles)
_profileService.LoadProfileConfigurationIcon(profileConfiguration);
ProfilePreview = profilePreviewViewModel; ProfilePreview = profilePreviewViewModel;
GoBack = ReactiveCommand.Create(() => State.ChangeScreen<EntryTypeStepViewModel>()); GoBack = ReactiveCommand.Create(() => State.ChangeScreen<EntryTypeStepViewModel>());
@ -70,7 +68,7 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel
State.EntrySource = new ProfileEntrySource(SelectedProfile, SelectedProfile.GetFeatureDependencies().Distinct().ToList()); State.EntrySource = new ProfileEntrySource(SelectedProfile, SelectedProfile.GetFeatureDependencies().Distinct().ToList());
State.Name = SelectedProfile.Name; State.Name = SelectedProfile.Name;
State.Icon = SelectedProfile.Icon.GetIconStream(); State.Icon = SelectedProfile.Icon.IconBytes != null ? new MemoryStream(SelectedProfile.Icon.IconBytes) : null;
// Render the material icon of the profile // Render the material icon of the profile
if (State.Icon == null && SelectedProfile.Icon.IconName != null) if (State.Icon == null && SelectedProfile.Icon.IconName != null)

View File

@ -74,7 +74,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler
try try
{ {
// Find the profile if still there // Find the profile if still there
ProfileConfiguration? profile = _profileService.ProfileConfigurations.FirstOrDefault(c => c.ProfileId == profileId); ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId);
if (profile != null) if (profile != null)
_profileService.DeleteProfile(profile); _profileService.DeleteProfile(profile);

View File

@ -1,16 +1,12 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using Artemis.Core; using Artemis.Core;
using Artemis.Storage.Entities.Workshop; using Artemis.Storage.Entities.Workshop;
using Artemis.WebClient.Workshop.Exceptions;
namespace Artemis.WebClient.Workshop.Models; namespace Artemis.WebClient.Workshop.Models;
public class InstalledEntry public class InstalledEntry
{ {
private static readonly JsonSerializerOptions JsonSerializerOptions = CoreJson.GetJsonSerializerOptions(); private Dictionary<string, object> _metadata = new();
private Dictionary<string, JsonNode> _metadata = new();
internal InstalledEntry(EntryEntity entity) internal InstalledEntry(EntryEntity entity)
{ {
@ -56,7 +52,7 @@ public class InstalledEntry
ReleaseVersion = Entity.ReleaseVersion; ReleaseVersion = Entity.ReleaseVersion;
InstalledAt = Entity.InstalledAt; InstalledAt = Entity.InstalledAt;
_metadata = Entity.Metadata != null ? new Dictionary<string, JsonNode>(Entity.Metadata) : new Dictionary<string, JsonNode>(); _metadata = Entity.Metadata != null ? new Dictionary<string, object>(Entity.Metadata) : new Dictionary<string, object>();
} }
internal void Save() internal void Save()
@ -71,7 +67,7 @@ public class InstalledEntry
Entity.ReleaseVersion = ReleaseVersion; Entity.ReleaseVersion = ReleaseVersion;
Entity.InstalledAt = InstalledAt; Entity.InstalledAt = InstalledAt;
Entity.Metadata = new Dictionary<string, JsonNode>(_metadata); Entity.Metadata = new Dictionary<string, object>(_metadata);
} }
/// <summary> /// <summary>
@ -84,29 +80,14 @@ public class InstalledEntry
/// <returns><see langword="true"/> if the metadata contains an element with the specified key; otherwise, <see langword="false"/>.</returns> /// <returns><see langword="true"/> if the metadata contains an element with the specified key; otherwise, <see langword="false"/>.</returns>
public bool TryGetMetadata<T>(string key, [NotNullWhen(true)] out T? value) public bool TryGetMetadata<T>(string key, [NotNullWhen(true)] out T? value)
{ {
if (!_metadata.TryGetValue(key, out JsonNode? jsonNode)) if (!_metadata.TryGetValue(key, out object? objectValue) || objectValue is not T result)
{ {
value = default; value = default;
return false; return false;
} }
try value = result;
{ return true;
T? deserialized = jsonNode.Deserialize<T>(JsonSerializerOptions);
if (deserialized != null)
{
value = deserialized;
return true;
}
value = default;
return false;
}
catch (Exception)
{
value = default;
return false;
}
} }
/// <summary> /// <summary>
@ -116,8 +97,7 @@ public class InstalledEntry
/// <param name="value">The value to set.</param> /// <param name="value">The value to set.</param>
public void SetMetadata(string key, object value) public void SetMetadata(string key, object value)
{ {
JsonNode? jsonNode = JsonSerializer.SerializeToNode(value, JsonSerializerOptions); _metadata[key] = value;
_metadata[key] = jsonNode ?? throw new ArtemisWorkshopException("Failed to serialize metadata value");
} }
/// <summary> /// <summary>

View File

@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Build.props = Directory.Build.props Directory.Build.props = Directory.Build.props
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Storage.Migrator", "Artemis.Storage.Migrator\Artemis.Storage.Migrator.csproj", "{D7B0966D-774A-40E4-9455-00C1261ACB6A}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
@ -71,6 +73,10 @@ Global
{2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Debug|x64.Build.0 = Debug|x64 {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Debug|x64.Build.0 = Debug|x64
{2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.ActiveCfg = Release|x64 {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.ActiveCfg = Release|x64
{2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.Build.0 = Release|x64 {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.Build.0 = Release|x64
{D7B0966D-774A-40E4-9455-00C1261ACB6A}.Debug|x64.ActiveCfg = Debug|Any CPU
{D7B0966D-774A-40E4-9455-00C1261ACB6A}.Debug|x64.Build.0 = Debug|Any CPU
{D7B0966D-774A-40E4-9455-00C1261ACB6A}.Release|x64.ActiveCfg = Release|Any CPU
{D7B0966D-774A-40E4-9455-00C1261ACB6A}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -30,6 +30,7 @@
<PackageVersion Include="Material.Icons.Avalonia" Version="2.1.0" /> <PackageVersion Include="Material.Icons.Avalonia" Version="2.1.0" />
<PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" /> <PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.2" /> <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" /> <PackageVersion Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />