diff --git a/src/Artemis.Core/DryIoc/ContainerExtensions.cs b/src/Artemis.Core/DryIoc/ContainerExtensions.cs index 5e057e049..ba0ede352 100644 --- a/src/Artemis.Core/DryIoc/ContainerExtensions.cs +++ b/src/Artemis.Core/DryIoc/ContainerExtensions.cs @@ -31,8 +31,9 @@ public static class ContainerExtensions // Bind storage container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton); + container.RegisterDelegate(() => StorageManager.CreateDbContext(Constants.DataFolder), Reuse.Transient); container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton); - + // Bind migrations container.RegisterMany(storageAssembly, type => type.IsAssignableTo(), Reuse.Singleton, nonPublicServiceTypes: true); diff --git a/src/Artemis.Core/Models/Profile/ProfileCategory.cs b/src/Artemis.Core/Models/Profile/ProfileCategory.cs index 4dc3f73e6..63dbafffa 100644 --- a/src/Artemis.Core/Models/Profile/ProfileCategory.cs +++ b/src/Artemis.Core/Models/Profile/ProfileCategory.cs @@ -175,7 +175,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel Order = Entity.Order; _profileConfigurations.Clear(); - foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations) + foreach (ProfileContainerEntity entityProfileConfiguration in Entity.ProfileConfigurations) _profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration)); } @@ -186,13 +186,10 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel Entity.IsCollapsed = IsCollapsed; Entity.IsSuspended = IsSuspended; Entity.Order = Order; - + Entity.ProfileConfigurations.Clear(); - foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) - { - profileConfiguration.Save(); + foreach (ProfileConfiguration profileConfiguration in _profileConfigurations) Entity.ProfileConfigurations.Add(profileConfiguration.Entity); - } } #endregion diff --git a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs index a4c1e89f4..521561610 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfiguration.cs @@ -37,13 +37,13 @@ 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..f2916190e 100644 --- a/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs +++ b/src/Artemis.Core/Models/ProfileConfiguration/ProfileConfigurationIcon.cs @@ -10,13 +10,13 @@ namespace Artemis.Core; /// public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel { - private readonly ProfileConfigurationEntity _entity; + private readonly ProfileContainerEntity _entity; private bool _fill; private string? _iconName; - private Stream? _iconStream; + private byte[]? _iconBytes; private ProfileConfigurationIconType _iconType; - internal ProfileConfigurationIcon(ProfileConfigurationEntity entity) + internal ProfileConfigurationIcon(ProfileContainerEntity entity) { _entity = entity; } @@ -48,6 +48,15 @@ public 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; + IconType = (ProfileConfigurationIconType) _entity.ProfileConfiguration.IconType; + Fill = _entity.ProfileConfiguration.IconFill; if (IconType != ProfileConfigurationIconType.MaterialIcon) return; - IconName = _entity.MaterialIcon; + IconName = _entity.ProfileConfiguration.MaterialIcon; + IconBytes = IconType == ProfileConfigurationIconType.BitmapImage ? _entity.Icon : null; + OnIconUpdated(); } /// public void Save() { - _entity.IconType = (int) IconType; - _entity.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null; - _entity.IconFill = Fill; + _entity.ProfileConfiguration.IconType = (int) IconType; + _entity.ProfileConfiguration.MaterialIcon = IconType == ProfileConfigurationIconType.MaterialIcon ? IconName : null; + _entity.ProfileConfiguration.IconFill = Fill; + _entity.Icon = IconBytes ?? Array.Empty(); } #endregion diff --git a/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs b/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs index a33844c1f..e27282063 100644 --- a/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/IPluginSetting.cs @@ -19,13 +19,7 @@ public interface IPluginSetting string Name { get; } /// - /// Determines whether the setting has been changed - /// - bool HasChanged { get; } - - /// - /// Gets or sets whether changes must automatically be saved - /// Note: When set to true is always false + /// Gets or sets whether changes must automatically be saved /// bool AutoSave { get; set; } diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs index 390566c76..e3510a58e 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs @@ -1,6 +1,5 @@ using System; using System.Text.Json; -using System.Text.Json.Nodes; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; @@ -24,7 +23,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting Name = pluginSettingEntity.Name; try { - _value = pluginSettingEntity.Value.Deserialize(IPluginSetting.SerializerOptions) ?? default!; + _value = CoreJson.Deserialize(pluginSettingEntity.Value)!; } catch (JsonException) { @@ -77,7 +76,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting public string Name { get; } /// - public bool HasChanged => !JsonNode.DeepEquals(JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions), _pluginSettingEntity.Value); + public bool HasChanged => CoreJson.Serialize(Value) != _pluginSettingEntity.Value; /// public bool AutoSave { get; set; } @@ -85,7 +84,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting /// public void RejectChanges() { - Value = _pluginSettingEntity.Value.Deserialize(IPluginSetting.SerializerOptions) ?? default!; + Value = CoreJson.Deserialize(_pluginSettingEntity.Value); } /// @@ -94,7 +93,7 @@ public class PluginSetting : CorePropertyChanged, IPluginSetting if (!HasChanged) return; - _pluginSettingEntity.Value = JsonSerializer.SerializeToNode(Value, IPluginSetting.SerializerOptions) ?? new JsonObject(); + _pluginSettingEntity.Value = CoreJson.Serialize(Value); _pluginRepository.SaveChanges(); OnSettingSaved(); } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 89a951149..b27a8601b 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -29,7 +29,6 @@ internal class CoreService : ICoreService // ReSharper disable UnusedParameter.Local public CoreService(IContainer container, ILogger logger, - StorageMigrationService _1, // injected to ensure migration runs early ISettingsService settingsService, IPluginManagementService pluginManagementService, IProfileService profileService, diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index afc949bba..d110c90a4 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -202,7 +202,7 @@ internal class DeviceService : IDeviceService _enabledDevices.Add(device); device.IsEnabled = true; device.Save(); - _deviceRepository.Save(device.DeviceEntity); + _deviceRepository.SaveChanges(); OnDeviceEnabled(new DeviceEventArgs(device)); UpdateLeds(); @@ -217,7 +217,7 @@ internal class DeviceService : IDeviceService _enabledDevices.Remove(device); device.IsEnabled = false; device.Save(); - _deviceRepository.Save(device.DeviceEntity); + _deviceRepository.SaveChanges(); OnDeviceDisabled(new DeviceEventArgs(device)); UpdateLeds(); @@ -227,7 +227,7 @@ internal class DeviceService : IDeviceService public void SaveDevice(ArtemisDevice artemisDevice) { artemisDevice.Save(); - _deviceRepository.Save(artemisDevice.DeviceEntity); + _deviceRepository.SaveChanges(); UpdateLeds(); } @@ -236,7 +236,7 @@ internal class DeviceService : IDeviceService { foreach (ArtemisDevice artemisDevice in _devices) artemisDevice.Save(); - _deviceRepository.Save(_devices.Select(d => d.DeviceEntity)); + _deviceRepository.SaveChanges(); 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); device = new ArtemisDevice(rgbDevice, deviceProvider); + _deviceRepository.Add(device.DeviceEntity); + _deviceRepository.SaveChanges(); } LoadDeviceLayout(device); diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 2f5d69638..033ed81ee 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -807,7 +807,7 @@ internal class PluginManagementService : IPluginManagementService plugin.Entity.Features.Add(featureInfo.Instance!.Entity); } - _pluginRepository.SavePlugin(plugin.Entity); + _pluginRepository.SaveChanges(); } #endregion diff --git a/src/Artemis.Core/Services/ScriptingService.cs b/src/Artemis.Core/Services/ScriptingService.cs index f768c2028..19011eefa 100644 --- a/src/Artemis.Core/Services/ScriptingService.cs +++ b/src/Artemis.Core/Services/ScriptingService.cs @@ -12,7 +12,7 @@ internal class ScriptingService : IScriptingService private readonly IPluginManagementService _pluginManagementService; private readonly IProfileService _profileService; private readonly List _scriptingProviders; - + public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService) { _pluginManagementService = pluginManagementService; @@ -29,10 +29,13 @@ 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/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index 2d2dfae7c..47766f5bb 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -16,11 +16,6 @@ public interface IProfileService : IArtemisService /// ReadOnlyCollection ProfileCategories { get; } - /// - /// Gets a read only collection containing all the profile configurations. - /// - ReadOnlyCollection ProfileConfigurations { get; } - /// /// Gets or sets the focused profile configuration which is rendered exclusively. /// @@ -101,16 +96,6 @@ public interface IProfileService : IArtemisService /// void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration); - /// - /// Loads the icon of this profile configuration if needed and puts it into ProfileConfiguration.Icon.FileIcon. - /// - void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration); - - /// - /// Saves the current icon of this profile. - /// - void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration); - /// /// Writes the profile to persistent storage. /// diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 5cafbd7db..bf6aa0f4f 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -25,7 +25,6 @@ internal class ProfileService : IProfileService private readonly IDeviceService _deviceService; private readonly List _pendingKeyboardEvents = new(); private readonly List _profileCategories; - private readonly IProfileRepository _profileRepository; private readonly List _profileMigrators; private readonly List _renderExceptions = new(); private readonly List _updateExceptions = new(); @@ -38,17 +37,17 @@ internal class ProfileService : IProfileService IPluginManagementService pluginManagementService, IInputService inputService, IDeviceService deviceService, - IProfileRepository profileRepository, List profileMigrators) { _logger = logger; _profileCategoryRepository = profileCategoryRepository; _pluginManagementService = pluginManagementService; _deviceService = deviceService; - _profileRepository = profileRepository; _profileMigrators = profileMigrators; _profileCategories = new List(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order)); + ProfileCategories = new ReadOnlyCollection(_profileCategories); + _deviceService.LedsChanged += DeviceServiceOnLedsChanged; _pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled; _pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled; @@ -166,50 +165,7 @@ internal class ProfileService : IProfileService } /// - public ReadOnlyCollection ProfileCategories - { - get - { - lock (_profileRepository) - { - return _profileCategories.AsReadOnly(); - } - } - } - - /// - public ReadOnlyCollection ProfileConfigurations - { - get - { - lock (_profileRepository) - { - return _profileCategories.SelectMany(c => c.ProfileConfigurations).ToList().AsReadOnly(); - } - } - } - - /// - public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration) - { - if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon) - return; - - using Stream? stream = _profileCategoryRepository.GetProfileIconStream(profileConfiguration.Entity.FileIconId); - if (stream != null) - profileConfiguration.Icon.SetIconByStream(stream); - } - - /// - public void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration) - { - if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon) - return; - - using Stream? stream = profileConfiguration.Icon.GetIconStream(); - if (stream != null) - _profileCategoryRepository.SaveProfileIconStream(profileConfiguration.Entity, stream); - } + public ReadOnlyCollection ProfileCategories { get; } /// public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration) @@ -226,21 +182,7 @@ 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) @@ -285,39 +227,34 @@ internal class ProfileService : IProfileService { DeactivateProfile(profileConfiguration); - ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); - if (profileEntity == null) - return; + ProfileCategory category = profileConfiguration.Category; - profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); - _profileRepository.Remove(profileEntity); - SaveProfileCategory(profileConfiguration.Category); + category.RemoveProfileConfiguration(profileConfiguration); + category.Entity.ProfileConfigurations.Remove(profileConfiguration.Entity); + + _profileCategoryRepository.SaveChanges(); } /// public ProfileCategory CreateProfileCategory(string name, bool addToTop = false) { ProfileCategory profileCategory; - lock (_profileRepository) + if (addToTop) { - if (addToTop) + profileCategory = new ProfileCategory(name, 1); + foreach (ProfileCategory category in _profileCategories) { - profileCategory = new ProfileCategory(name, 1); - foreach (ProfileCategory category in _profileCategories) - { - category.Order++; - category.Save(); - _profileCategoryRepository.Save(category.Entity); - } + category.Order++; + category.Save(); } - else - { - profileCategory = new ProfileCategory(name, _profileCategories.Count + 1); - } - - _profileCategories.Add(profileCategory); - SaveProfileCategory(profileCategory); } + else + { + profileCategory = new ProfileCategory(name, _profileCategories.Count + 1); + } + + _profileCategoryRepository.Add(profileCategory.Entity); + _profileCategories.Add(profileCategory); OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory)); return profileCategory; @@ -326,15 +263,11 @@ 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.Remove(profileCategory); + _profileCategoryRepository.Remove(profileCategory.Entity); OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory)); } @@ -343,24 +276,20 @@ 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); + SaveProfileCategory(category); return configuration; } /// public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration) { - profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration); + ProfileCategory category = profileConfiguration.Category; + category.RemoveProfileConfiguration(profileConfiguration); DeactivateProfile(profileConfiguration); SaveProfileCategory(profileConfiguration.Category); - ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId); - if (profileEntity != null) - _profileRepository.Remove(profileEntity); profileConfiguration.Dispose(); } @@ -369,7 +298,7 @@ internal class ProfileService : IProfileService public void SaveProfileCategory(ProfileCategory profileCategory) { profileCategory.Save(); - _profileCategoryRepository.Save(profileCategory.Entity); + _profileCategoryRepository.SaveChanges(); lock (_profileCategories) { @@ -392,11 +321,13 @@ internal class ProfileService : IProfileService profile.IsFreshImport = false; profile.ProfileEntity.IsFreshImport = false; - _profileRepository.Save(profile.ProfileEntity); + SaveProfileCategory(profile.Configuration.Category); // If the provided profile is external (cloned or from the workshop?) but it is loaded locally too, reload the local instance // A bit dodge but it ensures local instances always represent the latest stored version - ProfileConfiguration? localInstance = ProfileConfigurations.FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id); + ProfileConfiguration? localInstance = ProfileCategories + .SelectMany(c => c.ProfileConfigurations) + .FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id); if (localInstance == null) return; DeactivateProfile(localInstance); @@ -406,12 +337,8 @@ 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 +357,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); } } @@ -495,26 +421,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; + // A new GUID will be given on save + configurationEntity.FileIconId = Guid.Empty; + ProfileConfiguration profileConfiguration = new(category, containerEntity); + if (nameAffix != null) + profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}"; + + profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id; category.AddProfileConfiguration(profileConfiguration, targetIndex); List modules = _pluginManagementService.GetFeaturesOfType(); @@ -548,7 +474,7 @@ 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) @@ -565,7 +491,7 @@ internal class ProfileService : IProfileService return; configurationJson["Version"] ??= 0; - + foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version)) { if (profileMigrator.Version <= configurationJson["Version"]!.GetValue()) @@ -581,27 +507,27 @@ internal class ProfileService : IProfileService /// private void ActiveProfilesPopulateLeds() { - foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations) + foreach (ProfileCategory profileCategory in ProfileCategories) { - if (profileConfiguration.Profile == null) continue; - profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices); + foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) + { + if (profileConfiguration.Profile == null) continue; + profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices); - if (!profileConfiguration.Profile.IsFreshImport) continue; - _logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile); - AdaptProfile(profileConfiguration.Profile); + if (!profileConfiguration.Profile.IsFreshImport) continue; + _logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile); + AdaptProfile(profileConfiguration.Profile); + } } } private void UpdateModules() { - lock (_profileRepository) + List modules = _pluginManagementService.GetFeaturesOfType(); + foreach (ProfileCategory profileCategory in ProfileCategories) { - List modules = _pluginManagementService.GetFeaturesOfType(); - foreach (ProfileCategory profileCategory in _profileCategories) - { - foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) - profileConfiguration.LoadModules(modules); - } + foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations) + profileConfiguration.LoadModules(modules); } } diff --git a/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj b/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj new file mode 100644 index 000000000..f35f98a79 --- /dev/null +++ b/src/Artemis.Storage.Migrator/Artemis.Storage.Migrator.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/Artemis.Storage.Migrator/Program.cs b/src/Artemis.Storage.Migrator/Program.cs new file mode 100644 index 000000000..6ed140d3d --- /dev/null +++ b/src/Artemis.Storage.Migrator/Program.cs @@ -0,0 +1,21 @@ +using Artemis.Core.DryIoc; +using DryIoc; +using Microsoft.EntityFrameworkCore; + +namespace Artemis.Storage.Migrator; + +class Program +{ + static void Main(string[] args) + { + Container container = new Container(rules => rules + .WithMicrosoftDependencyInjectionRules() + .WithConcreteTypeDynamicRegistrations() + .WithoutThrowOnRegisteringDisposableTransient()); + + container.RegisterCore(); + + container.Resolve().Database.EnsureCreated(); + + } +} \ No newline at end of file diff --git a/src/Artemis.Storage.Migrator/artemis.db b/src/Artemis.Storage.Migrator/artemis.db new file mode 100644 index 000000000..286e00251 Binary files /dev/null and b/src/Artemis.Storage.Migrator/artemis.db differ diff --git a/src/Artemis.Storage/ArtemisDbContext.cs b/src/Artemis.Storage/ArtemisDbContext.cs index a49717bd9..2063bbcf1 100644 --- a/src/Artemis.Storage/ArtemisDbContext.cs +++ b/src/Artemis.Storage/ArtemisDbContext.cs @@ -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.Plugins; using Artemis.Storage.Entities.Profile; @@ -14,24 +18,40 @@ public class ArtemisDbContext : DbContext public DbSet Plugins => Set(); public DbSet PluginSettings => Set(); public DbSet ProfileCategories => Set(); - public DbSet Profiles => Set(); public DbSet Releases => Set(); + public string DataFolder { get; set; } = string.Empty; + public JsonSerializerOptions JsonSerializerOptions { get; set; } = JsonSerializerOptions.Default; + + /// + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite($"Data Source={Path.Combine(DataFolder, "artemis.db")}"); + } + /// protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .OwnsOne(d => d.InputIdentifiers, builder => builder.ToJson()) .OwnsOne(d => d.InputMappings, builder => builder.ToJson()); - - modelBuilder.Entity() - .OwnsOne(e => e.Metadata, builder => builder.ToJson()); - modelBuilder.Entity() - .OwnsOne(s => s.Value, builder => builder.ToJson()); + modelBuilder.Entity() + .Property(e => e.Metadata) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize>(v, JsonSerializerOptions) ?? new Dictionary()); modelBuilder.Entity() - .OwnsOne(c => c.ProfileConfiguration, builder => builder.ToJson()) - .OwnsOne(c => c.Profile, builder => builder.ToJson()); + .Property(e => e.ProfileConfiguration) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize(v, JsonSerializerOptions) ?? new ProfileConfigurationEntity()); + + modelBuilder.Entity() + .Property(e => e.Profile) + .HasConversion( + v => JsonSerializer.Serialize(v, JsonSerializerOptions), + v => JsonSerializer.Deserialize(v, JsonSerializerOptions) ?? new ProfileEntity()); } } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs index 611200781..0a0ca3b16 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginEntity.cs @@ -24,6 +24,8 @@ public 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 8be8cefb5..1d604c28a 100644 --- a/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs +++ b/src/Artemis.Storage/Entities/Plugins/PluginSettingEntity.cs @@ -1,5 +1,4 @@ using System; -using System.Text.Json.Nodes; namespace Artemis.Storage.Entities.Plugins; @@ -12,5 +11,5 @@ public class PluginSettingEntity public Guid PluginGuid { get; set; } public string Name { get; set; } = string.Empty; - public JsonNode Value { get; set; } = new JsonObject(); + public string Value { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs index 7bdee9ecf..02a797011 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileCategoryEntity.cs @@ -13,11 +13,4 @@ public class ProfileCategoryEntity public int Order { get; set; } public List ProfileConfigurations { get; set; } = new(); -} - -public class ProfileContainerEntity -{ - public byte[] Icon { get; set; } = Array.Empty(); - public ProfileConfigurationEntity ProfileConfiguration { get; set; } = new(); - public ProfileEntity Profile { get; set; } = new(); } \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Profile/ProfileContainerEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileContainerEntity.cs new file mode 100644 index 000000000..b6360ab76 --- /dev/null +++ b/src/Artemis.Storage/Entities/Profile/ProfileContainerEntity.cs @@ -0,0 +1,14 @@ +using System; + +namespace Artemis.Storage.Entities.Profile; + +public class ProfileContainerEntity +{ + public Guid Id { get; set; } + public byte[] Icon { get; set; } = Array.Empty(); + + public ProfileCategoryEntity ProfileCategory { get; set; } = null!; + + public ProfileConfigurationEntity ProfileConfiguration { get; set; } = new(); + public ProfileEntity Profile { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs index 73a063003..cd828721c 100644 --- a/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs +++ b/src/Artemis.Storage/Entities/Workshop/EntryEntity.cs @@ -18,5 +18,5 @@ public class EntryEntity public string ReleaseVersion { get; set; } = string.Empty; public DateTimeOffset InstalledAt { get; set; } - public Dictionary? Metadata { get; set; } + public Dictionary Metadata { get; set; } } \ No newline at end of file diff --git a/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs b/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs new file mode 100644 index 000000000..14b41beeb --- /dev/null +++ b/src/Artemis.Storage/Migrations/20240308203921_Initial.Designer.cs @@ -0,0 +1,323 @@ +// +using System; +using Artemis.Storage; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + [DbContext(typeof(ArtemisDbContext))] + [Migration("20240308203921_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Releases"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Plugins"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PluginEntityId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PluginEntityId"); + + b.ToTable("PluginFeatureEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PluginGuid") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PluginSettings"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCollapsed") + .HasColumnType("INTEGER"); + + b.Property("IsSuspended") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ProfileCategories"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Profile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileCategoryId") + .HasColumnType("TEXT"); + + b.Property("ProfileConfiguration") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProfileCategoryId"); + + b.ToTable("ProfileContainerEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BlueScale") + .HasColumnType("REAL"); + + b.Property("Categories") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceProvider") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GreenScale") + .HasColumnType("REAL"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LayoutParameter") + .HasColumnType("TEXT"); + + b.Property("LayoutType") + .HasColumnType("TEXT"); + + b.Property("LogicalLayout") + .HasColumnType("TEXT"); + + b.Property("PhysicalLayout") + .HasColumnType("INTEGER"); + + b.Property("RedScale") + .HasColumnType("REAL"); + + b.Property("Rotation") + .HasColumnType("REAL"); + + b.Property("Scale") + .HasColumnType("REAL"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("ZIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntryId") + .HasColumnType("INTEGER"); + + b.Property("EntryType") + .HasColumnType("INTEGER"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ReleaseId") + .HasColumnType("INTEGER"); + + b.Property("ReleaseVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null) + .WithMany("Features") + .HasForeignKey("PluginEntityId"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory") + .WithMany("ProfileConfigurations") + .HasForeignKey("ProfileCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProfileCategory"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.OwnsOne("System.Collections.Generic.List", "InputIdentifiers", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputIdentifiers"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.OwnsOne("System.Collections.Generic.List", "InputMappings", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputMappings"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.Navigation("InputIdentifiers") + .IsRequired(); + + b.Navigation("InputMappings") + .IsRequired(); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Navigation("ProfileConfigurations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Artemis.Storage/Migrations/20240308203921_Initial.cs b/src/Artemis.Storage/Migrations/20240308203921_Initial.cs new file mode 100644 index 000000000..607c92cc6 --- /dev/null +++ b/src/Artemis.Storage/Migrations/20240308203921_Initial.cs @@ -0,0 +1,194 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Devices", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + DeviceProvider = table.Column(type: "TEXT", nullable: false), + X = table.Column(type: "REAL", nullable: false), + Y = table.Column(type: "REAL", nullable: false), + Rotation = table.Column(type: "REAL", nullable: false), + Scale = table.Column(type: "REAL", nullable: false), + ZIndex = table.Column(type: "INTEGER", nullable: false), + RedScale = table.Column(type: "REAL", nullable: false), + GreenScale = table.Column(type: "REAL", nullable: false), + BlueScale = table.Column(type: "REAL", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false), + PhysicalLayout = table.Column(type: "INTEGER", nullable: false), + LogicalLayout = table.Column(type: "TEXT", nullable: true), + LayoutType = table.Column(type: "TEXT", nullable: true), + LayoutParameter = table.Column(type: "TEXT", nullable: true), + Categories = table.Column(type: "TEXT", nullable: false), + InputIdentifiers = table.Column(type: "TEXT", nullable: false), + InputMappings = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Devices", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Entries", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + EntryId = table.Column(type: "INTEGER", nullable: false), + EntryType = table.Column(type: "INTEGER", nullable: false), + Author = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + ReleaseId = table.Column(type: "INTEGER", nullable: false), + ReleaseVersion = table.Column(type: "TEXT", nullable: false), + InstalledAt = table.Column(type: "TEXT", nullable: false), + Metadata = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Entries", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Plugins", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Plugins", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PluginSettings", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + PluginGuid = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PluginSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ProfileCategories", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + IsCollapsed = table.Column(type: "INTEGER", nullable: false), + IsSuspended = table.Column(type: "INTEGER", nullable: false), + Order = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProfileCategories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Releases", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Version = table.Column(type: "TEXT", nullable: false), + InstalledAt = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Releases", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PluginFeatureEntity", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "TEXT", nullable: false), + IsEnabled = table.Column(type: "INTEGER", nullable: false), + PluginEntityId = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PluginFeatureEntity", x => x.Id); + table.ForeignKey( + name: "FK_PluginFeatureEntity_Plugins_PluginEntityId", + column: x => x.PluginEntityId, + principalTable: "Plugins", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "ProfileContainerEntity", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Icon = table.Column(type: "BLOB", nullable: false), + ProfileCategoryId = table.Column(type: "TEXT", nullable: false), + ProfileConfiguration = table.Column(type: "TEXT", nullable: false), + Profile = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProfileContainerEntity", x => x.Id); + table.ForeignKey( + name: "FK_ProfileContainerEntity_ProfileCategories_ProfileCategoryId", + column: x => x.ProfileCategoryId, + principalTable: "ProfileCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PluginFeatureEntity_PluginEntityId", + table: "PluginFeatureEntity", + column: "PluginEntityId"); + + migrationBuilder.CreateIndex( + name: "IX_ProfileContainerEntity_ProfileCategoryId", + table: "ProfileContainerEntity", + column: "ProfileCategoryId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Devices"); + + migrationBuilder.DropTable( + name: "Entries"); + + migrationBuilder.DropTable( + name: "PluginFeatureEntity"); + + migrationBuilder.DropTable( + name: "PluginSettings"); + + migrationBuilder.DropTable( + name: "ProfileContainerEntity"); + + migrationBuilder.DropTable( + name: "Releases"); + + migrationBuilder.DropTable( + name: "Plugins"); + + migrationBuilder.DropTable( + name: "ProfileCategories"); + } + } +} diff --git a/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs new file mode 100644 index 000000000..bd8df8c96 --- /dev/null +++ b/src/Artemis.Storage/Migrations/ArtemisDbContextModelSnapshot.cs @@ -0,0 +1,320 @@ +// +using System; +using Artemis.Storage; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Artemis.Storage.Migrations +{ + [DbContext(typeof(ArtemisDbContext))] + partial class ArtemisDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.2"); + + modelBuilder.Entity("Artemis.Storage.Entities.General.ReleaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Version") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Releases"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Plugins"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PluginEntityId") + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PluginEntityId"); + + b.ToTable("PluginFeatureEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PluginGuid") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PluginSettings"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCollapsed") + .HasColumnType("INTEGER"); + + b.Property("IsSuspended") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ProfileCategories"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Icon") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Profile") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileCategoryId") + .HasColumnType("TEXT"); + + b.Property("ProfileConfiguration") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProfileCategoryId"); + + b.ToTable("ProfileContainerEntity"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BlueScale") + .HasColumnType("REAL"); + + b.Property("Categories") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DeviceProvider") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GreenScale") + .HasColumnType("REAL"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LayoutParameter") + .HasColumnType("TEXT"); + + b.Property("LayoutType") + .HasColumnType("TEXT"); + + b.Property("LogicalLayout") + .HasColumnType("TEXT"); + + b.Property("PhysicalLayout") + .HasColumnType("INTEGER"); + + b.Property("RedScale") + .HasColumnType("REAL"); + + b.Property("Rotation") + .HasColumnType("REAL"); + + b.Property("Scale") + .HasColumnType("REAL"); + + b.Property("X") + .HasColumnType("REAL"); + + b.Property("Y") + .HasColumnType("REAL"); + + b.Property("ZIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Workshop.EntryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EntryId") + .HasColumnType("INTEGER"); + + b.Property("EntryType") + .HasColumnType("INTEGER"); + + b.Property("InstalledAt") + .HasColumnType("TEXT"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ReleaseId") + .HasColumnType("INTEGER"); + + b.Property("ReleaseVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginFeatureEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Plugins.PluginEntity", null) + .WithMany("Features") + .HasForeignKey("PluginEntityId"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileContainerEntity", b => + { + b.HasOne("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", "ProfileCategory") + .WithMany("ProfileConfigurations") + .HasForeignKey("ProfileCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ProfileCategory"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Surface.DeviceEntity", b => + { + b.OwnsOne("System.Collections.Generic.List", "InputIdentifiers", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputIdentifiers"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.OwnsOne("System.Collections.Generic.List", "InputMappings", b1 => + { + b1.Property("DeviceEntityId") + .HasColumnType("TEXT"); + + b1.Property("Capacity") + .HasColumnType("INTEGER"); + + b1.HasKey("DeviceEntityId"); + + b1.ToTable("Devices"); + + b1.ToJson("InputMappings"); + + b1.WithOwner() + .HasForeignKey("DeviceEntityId"); + }); + + b.Navigation("InputIdentifiers") + .IsRequired(); + + b.Navigation("InputMappings") + .IsRequired(); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Plugins.PluginEntity", b => + { + b.Navigation("Features"); + }); + + modelBuilder.Entity("Artemis.Storage.Entities.Profile.ProfileCategoryEntity", b => + { + b.Navigation("ProfileConfigurations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs deleted file mode 100644 index 5133c0328..000000000 --- a/src/Artemis.Storage/Repositories/Interfaces/IProfileRepository.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using Artemis.Storage.Entities.Profile; - -namespace Artemis.Storage.Repositories.Interfaces; - -public interface IProfileRepository : IRepository -{ - void Add(ProfileContainerEntity profileContainerEntity); - void Remove(ProfileContainerEntity profileContainerEntity); - List GetAll(); - ProfileContainerEntity? Get(Guid id); - void SaveChanges(); -} \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/ProfileRepository.cs b/src/Artemis.Storage/Repositories/ProfileRepository.cs deleted file mode 100644 index 0a2c1b88b..000000000 --- a/src/Artemis.Storage/Repositories/ProfileRepository.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Artemis.Storage.Entities.Profile; -using Artemis.Storage.Repositories.Interfaces; -using LiteDB; - -namespace Artemis.Storage.Repositories; - -internal class ProfileRepository : IProfileRepository -{ - private readonly ArtemisDbContext _dbContext; - - public ProfileRepository(ArtemisDbContext dbContext) - { - _dbContext = dbContext; - } - - public void Add(ProfileContainerEntity profileContainerEntity) - { - _dbContext.Profiles.Add(profileContainerEntity); - SaveChanges(); - } - - public void Remove(ProfileContainerEntity profileContainerEntity) - { - _dbContext.Profiles.Remove(profileContainerEntity); - SaveChanges(); - } - - public List GetAll() - { - return _dbContext.Profiles.ToList(); - } - - public ProfileContainerEntity? Get(Guid id) - { - return _dbContext.Profiles.FirstOrDefault(c => c.Profile.Id == id); - } - - public void SaveChanges() - { - _dbContext.SaveChanges(); - } -} \ No newline at end of file diff --git a/src/Artemis.Storage/StorageManager.cs b/src/Artemis.Storage/StorageManager.cs index 78fcc3643..7b612a927 100644 --- a/src/Artemis.Storage/StorageManager.cs +++ b/src/Artemis.Storage/StorageManager.cs @@ -59,4 +59,12 @@ public static class StorageManager throw new Exception($"LiteDB threw error code {e.ErrorCode}. See inner exception for more details", e); } } + + public static ArtemisDbContext CreateDbContext(string dataFolder) + { + return new ArtemisDbContext() + { + DataFolder = dataFolder + }; + } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs index dd652f0d8..7f93ea093 100644 --- a/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs +++ b/src/Artemis.UI.Shared/Controls/ProfileConfigurationIcon.axaml.cs @@ -46,17 +46,10 @@ public partial class ProfileConfigurationIcon : UserControl, IDisposable ? new MaterialIcon {Kind = parsedIcon!} : new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; } + else if (ConfigurationIcon.IconBytes != null) + Dispatcher.UIThread.Post(() => LoadFromBitmap(ConfigurationIcon, new MemoryStream(ConfigurationIcon.IconBytes)), DispatcherPriority.ApplicationIdle); else - { - Dispatcher.UIThread.Post(() => - { - Stream? stream = ConfigurationIcon?.GetIconStream(); - if (stream == null || ConfigurationIcon == null) - Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; - else - LoadFromBitmap(ConfigurationIcon, stream); - }, DispatcherPriority.ApplicationIdle); - } + Content = new MaterialIcon {Kind = MaterialIconKind.QuestionMark}; } catch (Exception) { diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 1b6af5381..eca2ee350 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -186,7 +186,7 @@ public partial class ProfileEditorViewModel : RoutableScreen public override async Task OnNavigating(ProfileEditorViewModelParameters parameters, NavigationArguments args, CancellationToken cancellationToken) { - ProfileConfiguration? profileConfiguration = _profileService.ProfileConfigurations.FirstOrDefault(c => c.ProfileId == parameters.ProfileId); + ProfileConfiguration? profileConfiguration = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == parameters.ProfileId); // If the profile doesn't exist, cancel navigation if (profileConfiguration == null) diff --git a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs index 1f1c9c500..aa79c5e75 100644 --- a/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs +++ b/src/Artemis.UI/Screens/Sidebar/Dialogs/ProfileConfigurationEditViewModel.cs @@ -123,7 +123,6 @@ public partial class ProfileConfigurationEditViewModel : DialogViewModelBase p == null) .ToProperty(this, vm => vm.IsDisabled) .DisposeWith(d)); - - try - { - _profileService.LoadProfileConfigurationIcon(ProfileConfiguration); - } - catch (Exception) - { - // ignored, too bad but don't crash over corrupt icons - } } public ProfileConfiguration ProfileConfiguration { get; } diff --git a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs index 4a5b9cae9..5afebcf4b 100644 --- a/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs +++ b/src/Artemis.UI/Screens/Workshop/SubmissionWizard/Steps/Profile/ProfileSelectionStepViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; @@ -27,10 +28,7 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel _profileService = profileService; // Use copies of the profiles, the originals are used by the core and could be disposed - Profiles = new ObservableCollection(_profileService.ProfileConfigurations.Select(_profileService.CloneProfileConfiguration)); - foreach (ProfileConfiguration profileConfiguration in Profiles) - _profileService.LoadProfileConfigurationIcon(profileConfiguration); - + Profiles = new ObservableCollection(_profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).Select(_profileService.CloneProfileConfiguration)); ProfilePreview = profilePreviewViewModel; GoBack = ReactiveCommand.Create(() => State.ChangeScreen()); @@ -70,7 +68,7 @@ public partial class ProfileSelectionStepViewModel : SubmissionViewModel State.EntrySource = new ProfileEntrySource(SelectedProfile, SelectedProfile.GetFeatureDependencies().Distinct().ToList()); State.Name = SelectedProfile.Name; - State.Icon = SelectedProfile.Icon.GetIconStream(); + State.Icon = SelectedProfile.Icon.IconBytes != null ? new MemoryStream(SelectedProfile.Icon.IconBytes) : null; // Render the material icon of the profile if (State.Icon == null && SelectedProfile.Icon.IconName != null) diff --git a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs index 218665d05..4ee9e0cb5 100644 --- a/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs +++ b/src/Artemis.WebClient.Workshop/Handlers/InstallationHandlers/Implementations/ProfileEntryInstallationHandler.cs @@ -74,7 +74,7 @@ public class ProfileEntryInstallationHandler : IEntryInstallationHandler try { // Find the profile if still there - ProfileConfiguration? profile = _profileService.ProfileConfigurations.FirstOrDefault(c => c.ProfileId == profileId); + ProfileConfiguration? profile = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(c => c.ProfileId == profileId); if (profile != null) _profileService.DeleteProfile(profile); diff --git a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs index 1f722cc5d..90aa4108e 100644 --- a/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs +++ b/src/Artemis.WebClient.Workshop/Models/InstalledEntry.cs @@ -1,16 +1,12 @@ using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using System.Text.Json.Nodes; using Artemis.Core; using Artemis.Storage.Entities.Workshop; -using Artemis.WebClient.Workshop.Exceptions; namespace Artemis.WebClient.Workshop.Models; public class InstalledEntry { - private static readonly JsonSerializerOptions JsonSerializerOptions = CoreJson.GetJsonSerializerOptions(); - private Dictionary _metadata = new(); + private Dictionary _metadata = new(); internal InstalledEntry(EntryEntity entity) { @@ -56,7 +52,7 @@ public class InstalledEntry ReleaseVersion = Entity.ReleaseVersion; InstalledAt = Entity.InstalledAt; - _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); + _metadata = Entity.Metadata != null ? new Dictionary(Entity.Metadata) : new Dictionary(); } internal void Save() @@ -71,7 +67,7 @@ public class InstalledEntry Entity.ReleaseVersion = ReleaseVersion; Entity.InstalledAt = InstalledAt; - Entity.Metadata = new Dictionary(_metadata); + Entity.Metadata = new Dictionary(_metadata); } /// @@ -84,29 +80,14 @@ public class InstalledEntry /// if the metadata contains an element with the specified key; otherwise, . public bool TryGetMetadata(string key, [NotNullWhen(true)] out T? value) { - if (!_metadata.TryGetValue(key, out JsonNode? jsonNode)) + if (!_metadata.TryGetValue(key, out object? objectValue) || objectValue is not T result) { value = default; return false; } - try - { - T? deserialized = jsonNode.Deserialize(JsonSerializerOptions); - if (deserialized != null) - { - value = deserialized; - return true; - } - - value = default; - return false; - } - catch (Exception) - { - value = default; - return false; - } + value = result; + return true; } /// @@ -116,8 +97,7 @@ public class InstalledEntry /// The value to set. public void SetMetadata(string key, object value) { - JsonNode? jsonNode = JsonSerializer.SerializeToNode(value, JsonSerializerOptions); - _metadata[key] = jsonNode ?? throw new ArtemisWorkshopException("Failed to serialize metadata value"); + _metadata[key] = value; } /// diff --git a/src/Artemis.sln b/src/Artemis.sln index e7643caba..984eb7997 100644 --- a/src/Artemis.sln +++ b/src/Artemis.sln @@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Storage.Migrator", "Artemis.Storage.Migrator\Artemis.Storage.Migrator.csproj", "{D7B0966D-774A-40E4-9455-00C1261ACB6A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -71,6 +73,10 @@ Global {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Debug|x64.Build.0 = Debug|x64 {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.ActiveCfg = Release|x64 {2B982C2E-3CBC-4DAB-9167-CCCA4C78E92B}.Release|x64.Build.0 = Release|x64 + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Debug|x64.ActiveCfg = Debug|Any CPU + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Debug|x64.Build.0 = Debug|Any CPU + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Release|x64.ActiveCfg = Release|Any CPU + {D7B0966D-774A-40E4-9455-00C1261ACB6A}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index f1830bf8a..b88396b71 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -30,6 +30,7 @@ +