mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 03:19:56 +00:00
Storage - Replace LiteDB with SQLite + EF Core (#843)
Storage - Added LiteDB to SQLite migration UI - Try to die a bit more gracefully Core - Delay start watching plugins for hot reload after initializing UI - Simplify category management logic UI - Avoid crash during profile icon load Storage - Fix entry metadata retrieval
This commit is contained in:
1 parent
9132301dbf
commit
551921db9f
130 files changed
+2965
-1135
No files matched your search
@@ -40,7 +40,7 @@
|
||||
<PackageReference Include="EmbedIO" />
|
||||
<PackageReference Include="HidSharp" />
|
||||
<PackageReference Include="Humanizer.Core" />
|
||||
<PackageReference Include="JetBrains.Annotations" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All"/>
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" />
|
||||
<PackageReference Include="RGB.NET.Core" />
|
||||
<PackageReference Include="RGB.NET.Layout" />
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Artemis.Core.JsonConverters;
|
||||
using Artemis.Storage.Entities.Plugins;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
@@ -90,7 +91,7 @@ public static class Constants
|
||||
/// <summary>
|
||||
/// The plugin used by core components of Artemis
|
||||
/// </summary>
|
||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
|
||||
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), new PluginEntity(){PluginGuid = CorePluginInfo.Guid}, false);
|
||||
|
||||
/// <summary>
|
||||
/// A read-only collection containing all primitive numeric types
|
||||
|
||||
@@ -22,20 +22,18 @@ public static class ContainerExtensions
|
||||
/// <param name="container">The builder building the current container</param>
|
||||
public static void RegisterCore(this IContainer container)
|
||||
{
|
||||
Assembly[] coreAssembly = {typeof(IArtemisService).Assembly};
|
||||
Assembly[] storageAssembly = {typeof(IRepository).Assembly};
|
||||
Assembly[] coreAssembly = [typeof(IArtemisService).Assembly];
|
||||
Assembly[] storageAssembly = [typeof(IRepository).Assembly];
|
||||
|
||||
// Bind all services as singletons
|
||||
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<IArtemisService>(), Reuse.Singleton);
|
||||
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<IProtectedArtemisService>(), Reuse.Singleton, setup: Setup.With(condition: HasAccessToProtectedService));
|
||||
|
||||
// Bind storage
|
||||
container.RegisterDelegate(() => StorageManager.CreateRepository(Constants.DataFolder), Reuse.Singleton);
|
||||
container.Register<StorageMigrationService>(Reuse.Singleton);
|
||||
container.RegisterDelegate(() => StorageManager.CreateDbContext(Constants.DataFolder), Reuse.Transient);
|
||||
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IRepository>(), Reuse.Singleton);
|
||||
|
||||
|
||||
// Bind migrations
|
||||
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IStorageMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
|
||||
container.RegisterMany(storageAssembly, type => type.IsAssignableTo<IProfileMigration>(), Reuse.Singleton, nonPublicServiceTypes: true);
|
||||
|
||||
container.RegisterMany(coreAssembly, type => type.IsAssignableTo<ILayoutProvider>(), Reuse.Singleton);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
|
||||
namespace Artemis.Core;
|
||||
@@ -15,7 +16,6 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
/// </summary>
|
||||
public static readonly ProfileCategory Empty = new("Empty", -1);
|
||||
|
||||
private readonly List<ProfileConfiguration> _profileConfigurations = new();
|
||||
private bool _isCollapsed;
|
||||
private bool _isSuspended;
|
||||
private string _name;
|
||||
@@ -31,14 +31,16 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
_name = name;
|
||||
_order = order;
|
||||
Entity = new ProfileCategoryEntity();
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>([]);
|
||||
|
||||
Save();
|
||||
}
|
||||
|
||||
internal ProfileCategory(ProfileCategoryEntity entity)
|
||||
{
|
||||
_name = null!;
|
||||
Entity = entity;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(_profileConfigurations);
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>([]);
|
||||
|
||||
Load();
|
||||
}
|
||||
@@ -83,7 +85,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
/// <summary>
|
||||
/// Gets a read only collection of the profiles inside this category
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
|
||||
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of this category
|
||||
@@ -96,29 +98,21 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
/// <summary>
|
||||
/// Adds a profile configuration to this category
|
||||
/// </summary>
|
||||
public void AddProfileConfiguration(ProfileConfiguration configuration, int? targetIndex)
|
||||
public void AddProfileConfiguration(ProfileConfiguration configuration, ProfileConfiguration? target)
|
||||
{
|
||||
// TODO: Look into this, it doesn't seem to make sense
|
||||
// Removing the original will shift every item in the list forwards, keep that in mind with the target index
|
||||
if (configuration.Category == this && targetIndex != null && targetIndex.Value > _profileConfigurations.IndexOf(configuration))
|
||||
targetIndex -= 1;
|
||||
|
||||
List<ProfileConfiguration> targetList = ProfileConfigurations.Where(c => c!= configuration).ToList();
|
||||
configuration.Category.RemoveProfileConfiguration(configuration);
|
||||
|
||||
if (targetIndex != null)
|
||||
{
|
||||
targetIndex = Math.Clamp(targetIndex.Value, 0, _profileConfigurations.Count);
|
||||
_profileConfigurations.Insert(targetIndex.Value, configuration);
|
||||
}
|
||||
|
||||
if (target != null)
|
||||
targetList.Insert(targetList.IndexOf(target), configuration);
|
||||
else
|
||||
{
|
||||
_profileConfigurations.Add(configuration);
|
||||
}
|
||||
targetList.Add(configuration);
|
||||
|
||||
configuration.Category = this;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(targetList);
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
for (int index = 0; index < ProfileConfigurations.Count; index++)
|
||||
ProfileConfigurations[index].Order = index;
|
||||
OnProfileConfigurationAdded(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
@@ -156,11 +150,10 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
|
||||
internal void RemoveProfileConfiguration(ProfileConfiguration configuration)
|
||||
{
|
||||
if (!_profileConfigurations.Remove(configuration))
|
||||
return;
|
||||
|
||||
for (int index = 0; index < _profileConfigurations.Count; index++)
|
||||
_profileConfigurations[index].Order = index;
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(ProfileConfigurations.Where(pc => pc != configuration).ToList());
|
||||
for (int index = 0; index < ProfileConfigurations.Count; index++)
|
||||
ProfileConfigurations[index].Order = index;
|
||||
|
||||
OnProfileConfigurationRemoved(new ProfileConfigurationEventArgs(configuration));
|
||||
}
|
||||
|
||||
@@ -174,9 +167,7 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
IsSuspended = Entity.IsSuspended;
|
||||
Order = Entity.Order;
|
||||
|
||||
_profileConfigurations.Clear();
|
||||
foreach (ProfileConfigurationEntity entityProfileConfiguration in Entity.ProfileConfigurations)
|
||||
_profileConfigurations.Add(new ProfileConfiguration(this, entityProfileConfiguration));
|
||||
ProfileConfigurations = new ReadOnlyCollection<ProfileConfiguration>(Entity.ProfileConfigurations.Select(pc => new ProfileConfiguration(this, pc)).ToList());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -186,13 +177,10 @@ public class ProfileCategory : CorePropertyChanged, IStorageModel
|
||||
Entity.IsCollapsed = IsCollapsed;
|
||||
Entity.IsSuspended = IsSuspended;
|
||||
Entity.Order = Order;
|
||||
|
||||
|
||||
Entity.ProfileConfigurations.Clear();
|
||||
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
|
||||
{
|
||||
profileConfiguration.Save();
|
||||
Entity.ProfileConfigurations.Add(profileConfiguration.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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<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
|
||||
_name = null!;
|
||||
@@ -192,12 +192,12 @@ public class ProfileConfiguration : BreakableModel, IStorageModel, IDisposable,
|
||||
/// <summary>
|
||||
/// Gets the entity used by this profile config
|
||||
/// </summary>
|
||||
public ProfileConfigurationEntity Entity { get; }
|
||||
public ProfileContainerEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the profile of this profile configuration
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -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
|
||||
|
||||
@@ -10,13 +10,13 @@ namespace Artemis.Core;
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Updates the <see cref="IconName" /> to the provided value and changes the <see cref="IconType" /> is
|
||||
/// <see cref="ProfileConfigurationIconType.MaterialIcon" />
|
||||
@@ -55,9 +64,9 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||
/// <param name="iconName">The name of the icon</param>
|
||||
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
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="stream">The stream to copy</param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Occurs when the icon was updated
|
||||
/// </summary>
|
||||
@@ -119,21 +113,24 @@ public class ProfileConfigurationIcon : CorePropertyChanged, IStorageModel
|
||||
/// <inheritdoc />
|
||||
public void Load()
|
||||
{
|
||||
IconType = (ProfileConfigurationIconType) _entity.IconType;
|
||||
Fill = _entity.IconFill;
|
||||
if (IconType != ProfileConfigurationIconType.MaterialIcon)
|
||||
return;
|
||||
IconType = (ProfileConfigurationIconType) _entity.ProfileConfiguration.IconType;
|
||||
Fill = _entity.ProfileConfiguration.IconFill;
|
||||
|
||||
IconName = _entity.MaterialIcon;
|
||||
if (IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
IconName = _entity.ProfileConfiguration.MaterialIcon;
|
||||
else
|
||||
IconBytes = _entity.Icon;
|
||||
|
||||
OnIconUpdated();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<byte>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -14,7 +14,7 @@ public class ArtemisDeviceInputIdentifier
|
||||
/// used by
|
||||
/// </param>
|
||||
/// <param name="identifier">A value used to identify the device</param>
|
||||
internal ArtemisDeviceInputIdentifier(string inputProvider, object identifier)
|
||||
internal ArtemisDeviceInputIdentifier(string inputProvider, string identifier)
|
||||
{
|
||||
InputProvider = inputProvider;
|
||||
Identifier = identifier;
|
||||
@@ -28,5 +28,5 @@ public class ArtemisDeviceInputIdentifier
|
||||
/// <summary>
|
||||
/// Gets or sets a value used to identify the device
|
||||
/// </summary>
|
||||
public object Identifier { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
}
|
||||
@@ -23,14 +23,14 @@ public class Plugin : CorePropertyChanged, IDisposable
|
||||
|
||||
private bool _isEnabled;
|
||||
|
||||
internal Plugin(PluginInfo info, DirectoryInfo directory, PluginEntity? pluginEntity)
|
||||
internal Plugin(PluginInfo info, DirectoryInfo directory, PluginEntity pluginEntity, bool loadedFromStorage)
|
||||
{
|
||||
Info = info;
|
||||
Directory = directory;
|
||||
Entity = pluginEntity ?? new PluginEntity {Id = Guid};
|
||||
Entity = pluginEntity;
|
||||
Info.Plugin = this;
|
||||
|
||||
_loadedFromStorage = pluginEntity != null;
|
||||
_loadedFromStorage = loadedFromStorage;
|
||||
_features = new List<PluginFeatureInfo>();
|
||||
_profilers = new List<Profiler>();
|
||||
|
||||
@@ -309,13 +309,7 @@ public class Plugin : CorePropertyChanged, IDisposable
|
||||
{
|
||||
FeatureRemoved?.Invoke(this, e);
|
||||
}
|
||||
|
||||
internal void ApplyToEntity()
|
||||
{
|
||||
Entity.Id = Guid;
|
||||
Entity.IsEnabled = IsEnabled;
|
||||
}
|
||||
|
||||
|
||||
internal void AddFeature(PluginFeatureInfo featureInfo)
|
||||
{
|
||||
if (featureInfo.Plugin != this)
|
||||
@@ -363,10 +357,10 @@ public class Plugin : CorePropertyChanged, IDisposable
|
||||
return Entity.Features.Any(f => f.IsEnabled) || Features.Any(f => f.AlwaysEnabled);
|
||||
}
|
||||
|
||||
internal void AutoEnableIfNew()
|
||||
internal bool AutoEnableIfNew()
|
||||
{
|
||||
if (_loadedFromStorage)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Enabled is preset to true if the plugin meets the following criteria
|
||||
// - Requires no admin rights
|
||||
@@ -377,11 +371,13 @@ public class Plugin : CorePropertyChanged, IDisposable
|
||||
Info.ArePrerequisitesMet();
|
||||
|
||||
if (!Entity.IsEnabled)
|
||||
return;
|
||||
return false;
|
||||
|
||||
// Also auto-enable any non-device provider feature
|
||||
foreach (PluginFeatureInfo pluginFeatureInfo in Features)
|
||||
pluginFeatureInfo.Entity.IsEnabled = !pluginFeatureInfo.FeatureType.IsAssignableTo(typeof(DeviceProvider));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
@@ -7,19 +8,18 @@ namespace Artemis.Core;
|
||||
/// </summary>
|
||||
public interface IPluginSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// The JSON serializer options used when serializing settings
|
||||
/// </summary>
|
||||
protected static readonly JsonSerializerOptions SerializerOptions = CoreJson.GetJsonSerializerOptions();
|
||||
|
||||
/// <summary>
|
||||
/// The name of the setting, unique to this plugin
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the setting has been changed
|
||||
/// </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>
|
||||
/// Gets or sets whether changes must automatically be saved
|
||||
/// </summary>
|
||||
bool AutoSave { get; set; }
|
||||
|
||||
|
||||
@@ -31,10 +31,13 @@ public class PluginSettings
|
||||
/// Gets the setting with the provided name. If the setting does not exist yet, it is created.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the setting, can be any serializable type</typeparam>
|
||||
/// <param name="name">The name of the setting</param>
|
||||
/// <param name="name">The name of the setting, may not be longer than 128 characters</param>
|
||||
/// <param name="defaultValue">The default value to use if the setting does not exist yet</param>
|
||||
public PluginSetting<T> GetSetting<T>(string name, T? defaultValue = default)
|
||||
{
|
||||
if (name.Length > 128)
|
||||
throw new ArtemisCoreException("Setting name cannot be longer than 128 characters");
|
||||
|
||||
lock (_settingEntities)
|
||||
{
|
||||
// Return cached value if available
|
||||
@@ -51,7 +54,7 @@ public class PluginSettings
|
||||
PluginGuid = Plugin.Guid,
|
||||
Value = CoreJson.Serialize(defaultValue)
|
||||
};
|
||||
_pluginRepository.AddSetting(settingEntity);
|
||||
_pluginRepository.SaveSetting(settingEntity);
|
||||
}
|
||||
|
||||
PluginSetting<T> pluginSetting = new(_pluginRepository, settingEntity);
|
||||
|
||||
@@ -29,7 +29,6 @@ internal class CoreService : ICoreService
|
||||
// ReSharper disable UnusedParameter.Local
|
||||
public CoreService(IContainer container,
|
||||
ILogger logger,
|
||||
StorageMigrationService _1, // injected to ensure migration runs early
|
||||
ISettingsService settingsService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IProfileService profileService,
|
||||
@@ -72,6 +71,7 @@ internal class CoreService : ICoreService
|
||||
// Initialize the services
|
||||
_pluginManagementService.CopyBuiltInPlugins();
|
||||
_pluginManagementService.LoadPlugins(IsElevated);
|
||||
_pluginManagementService.StartHotReload();
|
||||
_renderService.Initialize();
|
||||
|
||||
OnInitialized();
|
||||
|
||||
@@ -236,7 +236,7 @@ internal class DeviceService : IDeviceService
|
||||
{
|
||||
foreach (ArtemisDevice artemisDevice in _devices)
|
||||
artemisDevice.Save();
|
||||
_deviceRepository.Save(_devices.Select(d => d.DeviceEntity));
|
||||
_deviceRepository.SaveRange(_devices.Select(d => d.DeviceEntity));
|
||||
UpdateLeds();
|
||||
}
|
||||
|
||||
@@ -254,6 +254,7 @@ internal class DeviceService : IDeviceService
|
||||
{
|
||||
_logger.Information("No device config found for {DeviceInfo}, device hash: {DeviceHashCode}. Adding a new entry", rgbDevice.DeviceInfo, deviceIdentifier);
|
||||
device = new ArtemisDevice(rgbDevice, deviceProvider);
|
||||
_deviceRepository.Add(device.DeviceEntity);
|
||||
}
|
||||
|
||||
LoadDeviceLayout(device);
|
||||
|
||||
@@ -12,7 +12,7 @@ public class InputProviderIdentifierEventArgs : EventArgs
|
||||
/// </summary>
|
||||
/// <param name="identifier">A value that can be used to identify this device</param>
|
||||
/// <param name="deviceType">The type of device this identifier belongs to</param>
|
||||
public InputProviderIdentifierEventArgs(object identifier, InputDeviceType deviceType)
|
||||
public InputProviderIdentifierEventArgs(string identifier, InputDeviceType deviceType)
|
||||
{
|
||||
Identifier = identifier;
|
||||
DeviceType = deviceType;
|
||||
@@ -21,7 +21,7 @@ public class InputProviderIdentifierEventArgs : EventArgs
|
||||
/// <summary>
|
||||
/// Gets a value that can be used to identify this device
|
||||
/// </summary>
|
||||
public object Identifier { get; }
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of device this identifier belongs to
|
||||
|
||||
@@ -113,7 +113,7 @@ public abstract class InputProvider : IDisposable
|
||||
/// </summary>
|
||||
/// <param name="identifier">A value that can be used to identify this device</param>
|
||||
/// <param name="deviceType">The type of device this identifier belongs to</param>
|
||||
protected virtual void OnIdentifierReceived(object identifier, InputDeviceType deviceType)
|
||||
protected virtual void OnIdentifierReceived(string identifier, InputDeviceType deviceType)
|
||||
{
|
||||
IdentifierReceived?.Invoke(this, new InputProviderIdentifierEventArgs(identifier, deviceType));
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
||||
/// Gets a list containing additional directories in which plugins are located, used while loading plugins.
|
||||
/// </summary>
|
||||
List<DirectoryInfo> AdditionalPluginDirectories { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether or not plugins are currently being loaded
|
||||
/// </summary>
|
||||
@@ -33,6 +33,11 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
||||
/// </summary>
|
||||
void LoadPlugins(bool isElevated);
|
||||
|
||||
/// <summary>
|
||||
/// Starts monitoring plugin directories for changes and reloads plugins when changes are detected
|
||||
/// </summary>
|
||||
void StartHotReload();
|
||||
|
||||
/// <summary>
|
||||
/// Unloads all installed plugins.
|
||||
/// </summary>
|
||||
@@ -146,18 +151,6 @@ public interface IPluginManagementService : IArtemisService, IDisposable
|
||||
/// <returns></returns>
|
||||
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
||||
|
||||
/// <summary>
|
||||
/// Queues the provided plugin to be deleted the next time Artemis starts, before plugins are loaded
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin to delete</param>
|
||||
void QueuePluginDeletion(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided plugin for the deletion queue it was added to via <see cref="QueuePluginDeletion" />
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin to dequeue</param>
|
||||
void DequeuePluginDeletion(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when built-in plugins are being loaded
|
||||
/// </summary>
|
||||
|
||||
@@ -30,52 +30,17 @@ internal class PluginManagementService : IPluginManagementService
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPluginRepository _pluginRepository;
|
||||
private readonly List<Plugin> _plugins;
|
||||
private readonly IQueuedActionRepository _queuedActionRepository;
|
||||
private FileSystemWatcher? _hotReloadWatcher;
|
||||
private bool _disposed;
|
||||
private bool _isElevated;
|
||||
|
||||
public PluginManagementService(IContainer container, ILogger logger, IPluginRepository pluginRepository, IDeviceRepository deviceRepository, IQueuedActionRepository queuedActionRepository)
|
||||
public PluginManagementService(IContainer container, ILogger logger, IPluginRepository pluginRepository, IDeviceRepository deviceRepository)
|
||||
{
|
||||
_container = container;
|
||||
_logger = logger;
|
||||
_pluginRepository = pluginRepository;
|
||||
_deviceRepository = deviceRepository;
|
||||
_queuedActionRepository = queuedActionRepository;
|
||||
_plugins = new List<Plugin>();
|
||||
|
||||
ProcessPluginDeletionQueue();
|
||||
|
||||
StartHotReload();
|
||||
}
|
||||
|
||||
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
||||
{
|
||||
ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json");
|
||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory));
|
||||
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
||||
|
||||
// Remove the old directory if it exists
|
||||
if (Directory.Exists(pluginDirectory.FullName))
|
||||
pluginDirectory.Delete(true);
|
||||
|
||||
// Extract everything in the same archive directory to the unique plugin directory
|
||||
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);
|
||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
||||
foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries)
|
||||
{
|
||||
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/"))
|
||||
{
|
||||
string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
||||
// Create folders
|
||||
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
||||
// Extract files
|
||||
zipArchiveEntry.ExtractToFile(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (createLockFile)
|
||||
File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close();
|
||||
}
|
||||
|
||||
public List<DirectoryInfo> AdditionalPluginDirectories { get; } = new();
|
||||
@@ -159,6 +124,35 @@ internal class PluginManagementService : IPluginManagementService
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
||||
{
|
||||
ZipArchiveEntry metaDataFileEntry = zipArchive.Entries.First(e => e.Name == "plugin.json");
|
||||
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.PluginsFolder, targetDirectory));
|
||||
bool createLockFile = File.Exists(Path.Combine(pluginDirectory.FullName, "artemis.lock"));
|
||||
|
||||
// Remove the old directory if it exists
|
||||
if (Directory.Exists(pluginDirectory.FullName))
|
||||
pluginDirectory.Delete(true);
|
||||
|
||||
// Extract everything in the same archive directory to the unique plugin directory
|
||||
Utilities.CreateAccessibleDirectory(pluginDirectory.FullName);
|
||||
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
|
||||
foreach (ZipArchiveEntry zipArchiveEntry in zipArchive.Entries)
|
||||
{
|
||||
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory) && !zipArchiveEntry.FullName.EndsWith("/"))
|
||||
{
|
||||
string target = Path.Combine(pluginDirectory.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
|
||||
// Create folders
|
||||
Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(target)!);
|
||||
// Extract files
|
||||
zipArchiveEntry.ExtractToFile(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (createLockFile)
|
||||
File.Create(Path.Combine(pluginDirectory.FullName, "artemis.lock")).Close();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public List<Plugin> GetAllPlugins()
|
||||
@@ -376,7 +370,15 @@ internal class PluginManagementService : IPluginManagementService
|
||||
}
|
||||
|
||||
// Load the entity and fall back on creating a new one
|
||||
Plugin plugin = new(pluginInfo, directory, _pluginRepository.GetPluginByGuid(pluginInfo.Guid));
|
||||
PluginEntity? entity = _pluginRepository.GetPluginByPluginGuid(pluginInfo.Guid);
|
||||
bool loadedFromStorage = entity != null;
|
||||
if (entity == null)
|
||||
{
|
||||
entity = new PluginEntity {PluginGuid = pluginInfo.Guid};
|
||||
_pluginRepository.SavePlugin(entity);
|
||||
}
|
||||
|
||||
Plugin plugin = new(pluginInfo, directory, entity, loadedFromStorage);
|
||||
OnPluginLoading(new PluginEventArgs(plugin));
|
||||
|
||||
// Locate the main assembly entry
|
||||
@@ -440,7 +442,9 @@ internal class PluginManagementService : IPluginManagementService
|
||||
_logger.Warning("Plugin {plugin} contains no features", plugin);
|
||||
|
||||
// It is appropriate to call this now that we have the features of this plugin
|
||||
plugin.AutoEnableIfNew();
|
||||
bool autoEnabled = plugin.AutoEnableIfNew();
|
||||
if (autoEnabled)
|
||||
_pluginRepository.SavePlugin(entity);
|
||||
|
||||
List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(PluginBootstrapper).IsAssignableFrom(t)).ToList();
|
||||
if (bootstrappers.Count > 1)
|
||||
@@ -801,58 +805,6 @@ internal class PluginManagementService : IPluginManagementService
|
||||
|
||||
#endregion
|
||||
|
||||
#region Queued actions
|
||||
|
||||
public void QueuePluginDeletion(Plugin plugin)
|
||||
{
|
||||
_queuedActionRepository.Add(new QueuedActionEntity
|
||||
{
|
||||
Type = "DeletePlugin",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
{"pluginGuid", plugin.Guid.ToString()},
|
||||
{"plugin", plugin.ToString()},
|
||||
{"directory", plugin.Directory.FullName}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void DequeuePluginDeletion(Plugin plugin)
|
||||
{
|
||||
QueuedActionEntity? queuedActionEntity = _queuedActionRepository.GetByType("DeletePlugin").FirstOrDefault(q => q.Parameters["pluginGuid"].Equals(plugin.Guid.ToString()));
|
||||
if (queuedActionEntity != null)
|
||||
_queuedActionRepository.Remove(queuedActionEntity);
|
||||
}
|
||||
|
||||
private void ProcessPluginDeletionQueue()
|
||||
{
|
||||
foreach (QueuedActionEntity queuedActionEntity in _queuedActionRepository.GetByType("DeletePlugin"))
|
||||
{
|
||||
string? directory = queuedActionEntity.Parameters["directory"].ToString();
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(directory))
|
||||
{
|
||||
_logger.Information("Queued plugin deletion - deleting folder - {plugin}", queuedActionEntity.Parameters["plugin"]);
|
||||
Directory.Delete(directory!, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Information("Queued plugin deletion - folder already deleted - {plugin}", queuedActionEntity.Parameters["plugin"]);
|
||||
}
|
||||
|
||||
_queuedActionRepository.Remove(queuedActionEntity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, "Queued plugin deletion failed - {plugin}", queuedActionEntity.Parameters["plugin"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Storage
|
||||
|
||||
private void SavePlugin(Plugin plugin)
|
||||
@@ -942,7 +894,7 @@ internal class PluginManagementService : IPluginManagementService
|
||||
|
||||
#region Hot Reload
|
||||
|
||||
private void StartHotReload()
|
||||
public void StartHotReload()
|
||||
{
|
||||
// Watch for changes in the plugin directory, "plugin.json".
|
||||
// If this file is changed, reload the plugin.
|
||||
|
||||
@@ -40,7 +40,7 @@ internal class RenderService : IRenderService, IRenderer, IDisposable
|
||||
_graphicsContextProviders = graphicsContextProviders;
|
||||
|
||||
_targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30);
|
||||
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25);
|
||||
_renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5);
|
||||
_preferredGraphicsContext = settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
|
||||
_targetFrameRateSetting.SettingChanged += OnRenderSettingsChanged;
|
||||
_renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged;
|
||||
|
||||
@@ -12,7 +12,7 @@ internal class ScriptingService : IScriptingService
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly List<ScriptingProvider> _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<ScriptingProvider>());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public interface ISettingsService : IProtectedArtemisService
|
||||
/// Gets the setting with the provided name. If the setting does not exist yet, it is created.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the setting, can be any serializable type</typeparam>
|
||||
/// <param name="name">The name of the setting</param>
|
||||
/// <param name="name">The name of the setting, may not be longer than 128 characters</param>
|
||||
/// <param name="defaultValue">The default value to use if the setting does not exist yet</param>
|
||||
/// <returns></returns>
|
||||
PluginSetting<T> GetSetting<T>(string name, T? defaultValue = default);
|
||||
|
||||
@@ -16,11 +16,6 @@ public interface IProfileService : IArtemisService
|
||||
/// </summary>
|
||||
ReadOnlyCollection<ProfileCategory> ProfileCategories { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection containing all the profile configurations.
|
||||
/// </summary>
|
||||
ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the focused profile configuration which is rendered exclusively.
|
||||
/// </summary>
|
||||
@@ -59,13 +54,7 @@ public interface IProfileService : IArtemisService
|
||||
/// </summary>
|
||||
/// <param name="profileConfiguration">The profile configuration of the profile to activate.</param>
|
||||
void DeactivateProfile(ProfileConfiguration profileConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Permanently deletes the profile of the given <see cref="ProfileConfiguration" />.
|
||||
/// </summary>
|
||||
/// <param name="profileConfiguration">The profile configuration of the profile to delete.</param>
|
||||
void DeleteProfile(ProfileConfiguration profileConfiguration);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Saves the provided <see cref="ProfileCategory" /> and it's <see cref="ProfileConfiguration" />s but not the
|
||||
/// <see cref="Profile" />s themselves.
|
||||
@@ -101,16 +90,6 @@ public interface IProfileService : IArtemisService
|
||||
/// <param name="profileConfiguration"></param>
|
||||
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>
|
||||
/// Writes the profile to persistent storage.
|
||||
/// </summary>
|
||||
@@ -136,9 +115,9 @@ public interface IProfileService : IArtemisService
|
||||
/// any changes are made to it.
|
||||
/// </param>
|
||||
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash).</param>
|
||||
/// <param name="targetIndex">The index at which to import the profile into the category.</param>
|
||||
/// <param name="target">The profile before which to import the profile into the category.</param>
|
||||
/// <returns>The resulting profile configuration.</returns>
|
||||
Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported", int targetIndex = 0);
|
||||
Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix = "imported", ProfileConfiguration? target = null);
|
||||
|
||||
/// <summary>
|
||||
/// Imports the provided ZIP archive stream into the provided profile configuration
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
@@ -21,11 +22,10 @@ internal class ProfileService : IProfileService
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IProfileCategoryRepository _profileCategoryRepository;
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly List<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new();
|
||||
private readonly List<ProfileCategory> _profileCategories;
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly List<IProfileMigration> _profileMigrators;
|
||||
private readonly List<Exception> _renderExceptions = new();
|
||||
private readonly List<Exception> _updateExceptions = new();
|
||||
@@ -35,19 +35,20 @@ internal class ProfileService : IProfileService
|
||||
|
||||
public ProfileService(ILogger logger,
|
||||
IProfileCategoryRepository profileCategoryRepository,
|
||||
IProfileRepository profileRepository,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IInputService inputService,
|
||||
IDeviceService deviceService,
|
||||
IProfileRepository profileRepository,
|
||||
List<IProfileMigration> profileMigrators)
|
||||
{
|
||||
_logger = logger;
|
||||
_profileCategoryRepository = profileCategoryRepository;
|
||||
_profileRepository = profileRepository;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_deviceService = deviceService;
|
||||
_profileRepository = profileRepository;
|
||||
_profileMigrators = profileMigrators;
|
||||
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
|
||||
|
||||
ProfileCategories = new ReadOnlyCollection<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order).ToList());
|
||||
|
||||
_deviceService.LedsChanged += DeviceServiceOnLedsChanged;
|
||||
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||
@@ -55,7 +56,7 @@ internal class ProfileService : IProfileService
|
||||
|
||||
inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
|
||||
|
||||
if (!_profileCategories.Any())
|
||||
if (!ProfileCategories.Any())
|
||||
CreateDefaultProfileCategories();
|
||||
UpdateModules();
|
||||
}
|
||||
@@ -77,55 +78,52 @@ internal class ProfileService : IProfileService
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_profileCategories)
|
||||
// Iterate the children in reverse because the first category must be rendered last to end up on top
|
||||
for (int i = ProfileCategories.Count - 1; i > -1; i--)
|
||||
{
|
||||
// Iterate the children in reverse because the first category must be rendered last to end up on top
|
||||
for (int i = _profileCategories.Count - 1; i > -1; i--)
|
||||
ProfileCategory profileCategory = ProfileCategories[i];
|
||||
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||
{
|
||||
ProfileCategory profileCategory = _profileCategories[i];
|
||||
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||
|
||||
// Process hotkeys that where pressed since this profile last updated
|
||||
ProcessPendingKeyEvents(profileConfiguration);
|
||||
|
||||
bool shouldBeActive = profileConfiguration.ShouldBeActive(false);
|
||||
if (shouldBeActive)
|
||||
{
|
||||
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||
profileConfiguration.Update();
|
||||
shouldBeActive = profileConfiguration.ActivationConditionMet;
|
||||
}
|
||||
|
||||
// Process hotkeys that where pressed since this profile last updated
|
||||
ProcessPendingKeyEvents(profileConfiguration);
|
||||
|
||||
bool shouldBeActive = profileConfiguration.ShouldBeActive(false);
|
||||
if (shouldBeActive)
|
||||
try
|
||||
{
|
||||
// Make sure the profile is active or inactive according to the parameters above
|
||||
if (shouldBeActive && profileConfiguration.Profile == null && profileConfiguration.BrokenState != "Failed to activate profile")
|
||||
profileConfiguration.TryOrBreak(() => ActivateProfile(profileConfiguration), "Failed to activate profile");
|
||||
if (shouldBeActive && profileConfiguration.Profile != null && !profileConfiguration.Profile.ShouldDisplay)
|
||||
profileConfiguration.Profile.ShouldDisplay = true;
|
||||
else if (!shouldBeActive && profileConfiguration.Profile != null)
|
||||
{
|
||||
profileConfiguration.Update();
|
||||
shouldBeActive = profileConfiguration.ActivationConditionMet;
|
||||
if (!profileConfiguration.FadeInAndOut)
|
||||
DeactivateProfile(profileConfiguration);
|
||||
else if (!profileConfiguration.Profile.ShouldDisplay && profileConfiguration.Profile.Opacity <= 0)
|
||||
DeactivateProfile(profileConfiguration);
|
||||
else if (profileConfiguration.Profile.Opacity > 0)
|
||||
RequestDeactivation(profileConfiguration);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure the profile is active or inactive according to the parameters above
|
||||
if (shouldBeActive && profileConfiguration.Profile == null && profileConfiguration.BrokenState != "Failed to activate profile")
|
||||
profileConfiguration.TryOrBreak(() => ActivateProfile(profileConfiguration), "Failed to activate profile");
|
||||
if (shouldBeActive && profileConfiguration.Profile != null && !profileConfiguration.Profile.ShouldDisplay)
|
||||
profileConfiguration.Profile.ShouldDisplay = true;
|
||||
else if (!shouldBeActive && profileConfiguration.Profile != null)
|
||||
{
|
||||
if (!profileConfiguration.FadeInAndOut)
|
||||
DeactivateProfile(profileConfiguration);
|
||||
else if (!profileConfiguration.Profile.ShouldDisplay && profileConfiguration.Profile.Opacity <= 0)
|
||||
DeactivateProfile(profileConfiguration);
|
||||
else if (profileConfiguration.Profile.Opacity > 0)
|
||||
RequestDeactivation(profileConfiguration);
|
||||
}
|
||||
|
||||
profileConfiguration.Profile?.Update(deltaTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_updateExceptions.Add(e);
|
||||
}
|
||||
profileConfiguration.Profile?.Update(deltaTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_updateExceptions.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
LogProfileUpdateExceptions();
|
||||
_pendingKeyboardEvents.Clear();
|
||||
}
|
||||
|
||||
LogProfileUpdateExceptions();
|
||||
_pendingKeyboardEvents.Clear();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -138,78 +136,32 @@ internal class ProfileService : IProfileService
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_profileCategories)
|
||||
// Iterate the children in reverse because the first category must be rendered last to end up on top
|
||||
for (int i = ProfileCategories.Count - 1; i > -1; i--)
|
||||
{
|
||||
// Iterate the children in reverse because the first category must be rendered last to end up on top
|
||||
for (int i = _profileCategories.Count - 1; i > -1; i--)
|
||||
ProfileCategory profileCategory = ProfileCategories[i];
|
||||
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||
{
|
||||
ProfileCategory profileCategory = _profileCategories[i];
|
||||
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||
// Ensure all criteria are met before rendering
|
||||
bool fadingOut = profileConfiguration.Profile?.ShouldDisplay == false && profileConfiguration.Profile?.Opacity > 0;
|
||||
if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && (profileConfiguration.ActivationConditionMet || fadingOut))
|
||||
profileConfiguration.Profile?.Render(canvas, SKPointI.Empty, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_renderExceptions.Add(e);
|
||||
}
|
||||
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||
// Ensure all criteria are met before rendering
|
||||
bool fadingOut = profileConfiguration.Profile?.ShouldDisplay == false && profileConfiguration.Profile?.Opacity > 0;
|
||||
if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && (profileConfiguration.ActivationConditionMet || fadingOut))
|
||||
profileConfiguration.Profile?.Render(canvas, SKPointI.Empty, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_renderExceptions.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
LogProfileRenderExceptions();
|
||||
}
|
||||
|
||||
LogProfileRenderExceptions();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlyCollection<ProfileCategory> ProfileCategories
|
||||
{
|
||||
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);
|
||||
}
|
||||
public ReadOnlyCollection<ProfileCategory> ProfileCategories { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProfileConfiguration CloneProfileConfiguration(ProfileConfiguration profileConfiguration)
|
||||
@@ -226,21 +178,7 @@ internal class ProfileService : IProfileService
|
||||
return profileConfiguration.Profile;
|
||||
}
|
||||
|
||||
ProfileEntity? profileEntity;
|
||||
try
|
||||
{
|
||||
profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
profileConfiguration.SetBrokenState("Failed to activate profile", e);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}");
|
||||
|
||||
Profile profile = new(profileConfiguration, profileEntity);
|
||||
Profile profile = new(profileConfiguration, profileConfiguration.Entity.Profile);
|
||||
profile.PopulateLeds(_deviceService.EnabledDevices);
|
||||
|
||||
if (profile.IsFreshImport)
|
||||
@@ -279,45 +217,29 @@ internal class ProfileService : IProfileService
|
||||
|
||||
profileConfiguration.Profile.ShouldDisplay = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DeleteProfile(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
DeactivateProfile(profileConfiguration);
|
||||
|
||||
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||
if (profileEntity == null)
|
||||
return;
|
||||
|
||||
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);
|
||||
_profileRepository.Remove(profileEntity);
|
||||
SaveProfileCategory(profileConfiguration.Category);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProfileCategory CreateProfileCategory(string name, bool addToTop = false)
|
||||
{
|
||||
ProfileCategory profileCategory;
|
||||
lock (_profileRepository)
|
||||
if (addToTop)
|
||||
{
|
||||
if (addToTop)
|
||||
profileCategory = new ProfileCategory(name, 1);
|
||||
foreach (ProfileCategory category in ProfileCategories)
|
||||
{
|
||||
profileCategory = new ProfileCategory(name, 1);
|
||||
foreach (ProfileCategory category in _profileCategories)
|
||||
{
|
||||
category.Order++;
|
||||
category.Save();
|
||||
_profileCategoryRepository.Save(category.Entity);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
|
||||
category.Order++;
|
||||
category.Save();
|
||||
}
|
||||
|
||||
_profileCategories.Add(profileCategory);
|
||||
SaveProfileCategory(profileCategory);
|
||||
_profileCategoryRepository.SaveRange(ProfileCategories.Select(c => c.Entity).ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
profileCategory = new ProfileCategory(name, ProfileCategories.Count + 1);
|
||||
}
|
||||
|
||||
_profileCategoryRepository.Add(profileCategory.Entity);
|
||||
ProfileCategories = new ReadOnlyCollection<ProfileCategory>([..ProfileCategories, profileCategory]);
|
||||
|
||||
OnProfileCategoryAdded(new ProfileCategoryEventArgs(profileCategory));
|
||||
return profileCategory;
|
||||
@@ -326,15 +248,11 @@ internal class ProfileService : IProfileService
|
||||
/// <inheritdoc />
|
||||
public void DeleteProfileCategory(ProfileCategory profileCategory)
|
||||
{
|
||||
List<ProfileConfiguration> profileConfigurations = profileCategory.ProfileConfigurations.ToList();
|
||||
foreach (ProfileConfiguration profileConfiguration in profileConfigurations)
|
||||
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations.ToList())
|
||||
RemoveProfileConfiguration(profileConfiguration);
|
||||
|
||||
lock (_profileRepository)
|
||||
{
|
||||
_profileCategories.Remove(profileCategory);
|
||||
_profileCategoryRepository.Remove(profileCategory.Entity);
|
||||
}
|
||||
ProfileCategories = new ReadOnlyCollection<ProfileCategory>(ProfileCategories.Where(c => c != profileCategory).ToList());
|
||||
_profileCategoryRepository.Remove(profileCategory.Entity);
|
||||
|
||||
OnProfileCategoryRemoved(new ProfileCategoryEventArgs(profileCategory));
|
||||
}
|
||||
@@ -343,26 +261,24 @@ internal class ProfileService : IProfileService
|
||||
public ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon)
|
||||
{
|
||||
ProfileConfiguration configuration = new(category, name, icon);
|
||||
ProfileEntity entity = new();
|
||||
_profileRepository.Add(entity);
|
||||
|
||||
configuration.Entity.ProfileId = entity.Id;
|
||||
category.AddProfileConfiguration(configuration, 0);
|
||||
category.AddProfileConfiguration(configuration, category.ProfileConfigurations.FirstOrDefault());
|
||||
SaveProfileCategory(category);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveProfileConfiguration(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);
|
||||
|
||||
DeactivateProfile(profileConfiguration);
|
||||
SaveProfileCategory(profileConfiguration.Category);
|
||||
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||
if (profileEntity != null)
|
||||
_profileRepository.Remove(profileEntity);
|
||||
|
||||
profileConfiguration.Dispose();
|
||||
ProfileCategory category = profileConfiguration.Category;
|
||||
|
||||
category.RemoveProfileConfiguration(profileConfiguration);
|
||||
category.Save();
|
||||
|
||||
_profileRepository.Remove(profileConfiguration.Entity);
|
||||
_profileCategoryRepository.Save(category.Entity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -370,11 +286,7 @@ internal class ProfileService : IProfileService
|
||||
{
|
||||
profileCategory.Save();
|
||||
_profileCategoryRepository.Save(profileCategory.Entity);
|
||||
|
||||
lock (_profileCategories)
|
||||
{
|
||||
_profileCategories.Sort((a, b) => a.Order - b.Order);
|
||||
}
|
||||
ProfileCategories = new ReadOnlyCollection<ProfileCategory>(ProfileCategories.OrderBy(c => c.Order).ToList());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -392,13 +304,16 @@ internal class ProfileService : IProfileService
|
||||
profile.IsFreshImport = false;
|
||||
profile.ProfileEntity.IsFreshImport = false;
|
||||
|
||||
_profileRepository.Save(profile.ProfileEntity);
|
||||
_profileRepository.Save(profile.Configuration.Entity);
|
||||
|
||||
// If the provided profile is external (cloned or from the workshop?) but it is loaded locally too, reload the local instance
|
||||
// A bit dodge but it ensures local instances always represent the latest stored version
|
||||
ProfileConfiguration? localInstance = ProfileConfigurations.FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id);
|
||||
ProfileConfiguration? localInstance = ProfileCategories
|
||||
.SelectMany(c => c.ProfileConfigurations)
|
||||
.FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id);
|
||||
if (localInstance == null)
|
||||
return;
|
||||
|
||||
DeactivateProfile(localInstance);
|
||||
ActivateProfile(localInstance);
|
||||
}
|
||||
@@ -406,12 +321,8 @@ internal class ProfileService : IProfileService
|
||||
/// <inheritdoc />
|
||||
public async Task<Stream> ExportProfile(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileEntity? profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisCoreException("Could not locate profile entity");
|
||||
|
||||
string configurationJson = CoreJson.Serialize(profileConfiguration.Entity);
|
||||
string profileJson = CoreJson.Serialize(profileEntity);
|
||||
string configurationJson = CoreJson.Serialize(profileConfiguration.Entity.ProfileConfiguration);
|
||||
string profileJson = CoreJson.Serialize(profileConfiguration.Entity.Profile);
|
||||
|
||||
MemoryStream archiveStream = new();
|
||||
|
||||
@@ -430,12 +341,11 @@ internal class ProfileService : IProfileService
|
||||
await entryStream.WriteAsync(Encoding.Default.GetBytes(profileJson));
|
||||
}
|
||||
|
||||
await using Stream? iconStream = profileConfiguration.Icon.GetIconStream();
|
||||
if (iconStream != null)
|
||||
if (profileConfiguration.Icon.IconBytes != null)
|
||||
{
|
||||
ZipArchiveEntry iconEntry = archive.CreateEntry("icon.png");
|
||||
await using Stream entryStream = iconEntry.Open();
|
||||
await iconStream.CopyToAsync(entryStream);
|
||||
await entryStream.WriteAsync(profileConfiguration.Icon.IconBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +354,7 @@ internal class ProfileService : IProfileService
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix, int targetIndex = 0)
|
||||
public async Task<ProfileConfiguration> ImportProfile(Stream archiveStream, ProfileCategory category, bool makeUnique, bool markAsFreshImport, string? nameAffix, ProfileConfiguration? target)
|
||||
{
|
||||
using ZipArchive archive = new(archiveStream, ZipArchiveMode.Read, true);
|
||||
|
||||
@@ -468,7 +378,7 @@ internal class ProfileService : IProfileService
|
||||
JsonObject? profileJson = CoreJson.Deserialize<JsonObject>(await profileReader.ReadToEndAsync());
|
||||
|
||||
// Before deserializing, apply any pending migrations
|
||||
MigrateProfile(configurationJson, profileJson);
|
||||
_profileRepository.MigrateProfile(configurationJson, profileJson);
|
||||
|
||||
// Deserialize profile configuration to ProfileConfigurationEntity
|
||||
ProfileConfigurationEntity? configurationEntity = configurationJson?.Deserialize<ProfileConfigurationEntity>(Constants.JsonConvertSettings);
|
||||
@@ -495,27 +405,26 @@ internal class ProfileService : IProfileService
|
||||
if (markAsFreshImport)
|
||||
profileEntity.IsFreshImport = true;
|
||||
|
||||
if (_profileRepository.Get(profileEntity.Id) == null)
|
||||
_profileRepository.Add(profileEntity);
|
||||
else
|
||||
if (makeUnique && ProfileCategories.SelectMany(c => c.ProfileConfigurations).Any(c => c.ProfileId == profileEntity.Id))
|
||||
throw new ArtemisCoreException($"Cannot import this profile without {nameof(makeUnique)} being true");
|
||||
|
||||
// A new GUID will be given on save
|
||||
configurationEntity.FileIconId = Guid.Empty;
|
||||
ProfileConfiguration profileConfiguration = new(category, configurationEntity);
|
||||
if (nameAffix != null)
|
||||
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
|
||||
|
||||
ProfileContainerEntity containerEntity = new() {ProfileConfiguration = configurationEntity, Profile = profileEntity};
|
||||
// If an icon was provided, import that as well
|
||||
if (iconEntry != null)
|
||||
{
|
||||
await using Stream iconStream = iconEntry.Open();
|
||||
profileConfiguration.Icon.SetIconByStream(iconStream);
|
||||
SaveProfileConfigurationIcon(profileConfiguration);
|
||||
using MemoryStream ms = new();
|
||||
await iconStream.CopyToAsync(ms);
|
||||
containerEntity.Icon = ms.ToArray();
|
||||
}
|
||||
|
||||
profileConfiguration.Entity.ProfileId = profileEntity.Id;
|
||||
category.AddProfileConfiguration(profileConfiguration, targetIndex);
|
||||
// A new GUID will be given on save
|
||||
ProfileConfiguration profileConfiguration = new(category, containerEntity);
|
||||
if (nameAffix != null)
|
||||
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
|
||||
|
||||
profileConfiguration.Entity.ProfileConfiguration.ProfileId = profileEntity.Id;
|
||||
category.AddProfileConfiguration(profileConfiguration, target);
|
||||
|
||||
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
|
||||
profileConfiguration.LoadModules(modules);
|
||||
@@ -527,9 +436,9 @@ internal class ProfileService : IProfileService
|
||||
/// <inheritdoc />
|
||||
public async Task<ProfileConfiguration> OverwriteProfile(MemoryStream archiveStream, ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration.Order + 1);
|
||||
ProfileConfiguration imported = await ImportProfile(archiveStream, profileConfiguration.Category, true, true, null, profileConfiguration);
|
||||
|
||||
DeleteProfile(profileConfiguration);
|
||||
RemoveProfileConfiguration(profileConfiguration);
|
||||
SaveProfileCategory(imported.Category);
|
||||
|
||||
return imported;
|
||||
@@ -548,60 +457,40 @@ internal class ProfileService : IProfileService
|
||||
renderProfileElement.Save();
|
||||
|
||||
_logger.Debug("Adapt profile - Saving " + profile);
|
||||
_profileRepository.Save(profile.ProfileEntity);
|
||||
SaveProfileCategory(profile.Configuration.Category);
|
||||
}
|
||||
|
||||
private void InputServiceOnKeyboardKeyUp(object? sender, ArtemisKeyboardKeyEventArgs e)
|
||||
{
|
||||
lock (_profileCategories)
|
||||
{
|
||||
_pendingKeyboardEvents.Add(e);
|
||||
}
|
||||
_pendingKeyboardEvents.Add(e);
|
||||
}
|
||||
|
||||
private void MigrateProfile(JsonObject? configurationJson, JsonObject? profileJson)
|
||||
{
|
||||
if (configurationJson == null || profileJson == null)
|
||||
return;
|
||||
|
||||
configurationJson["Version"] ??= 0;
|
||||
|
||||
foreach (IProfileMigration profileMigrator in _profileMigrators.OrderBy(m => m.Version))
|
||||
{
|
||||
if (profileMigrator.Version <= configurationJson["Version"]!.GetValue<int>())
|
||||
continue;
|
||||
|
||||
profileMigrator.Migrate(configurationJson, profileJson);
|
||||
configurationJson["Version"] = profileMigrator.Version;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Populates all missing LEDs on all currently active profiles
|
||||
/// </summary>
|
||||
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<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
|
||||
foreach (ProfileCategory profileCategory in ProfileCategories)
|
||||
{
|
||||
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>();
|
||||
foreach (ProfileCategory profileCategory in _profileCategories)
|
||||
{
|
||||
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
|
||||
profileConfiguration.LoadModules(modules);
|
||||
}
|
||||
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
|
||||
profileConfiguration.LoadModules(modules);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user