mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Profiles - Reworked profile system
Sidebar - Redesigned sidebar with customizable categories Profiles - Added the ability to configure custom profile icons Profiles - Added the ability to activate multiple profiles for modules at once Profiles - Added the ability to create profiles for no modules Profiles - Added the ability to suspend a profile or an entire category Profiles - Added profile activation conditions Profiles - Added file-based importing/exporting Profile editor - Condensed UI, removed tabs Profile editor - Disable condition operators until a left-side is picked
This commit is contained in:
1 parent
4152c290d2
commit
ceeaa4bf6d
164 files changed
+5019
-4220
No files matched your search
@@ -29,11 +29,10 @@ namespace Artemis.Core.Services
|
||||
private readonly PluginSetting<LogEventLevel> _loggingLevel;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly IModuleService _moduleService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly List<Exception> _updateExceptions = new();
|
||||
private List<BaseDataModelExpansion> _dataModelExpansions = new();
|
||||
private DateTime _lastExceptionLog;
|
||||
private List<Module> _modules = new();
|
||||
|
||||
// ReSharper disable UnusedParameter.Local
|
||||
public CoreService(IKernel kernel,
|
||||
@@ -43,8 +42,7 @@ namespace Artemis.Core.Services
|
||||
IPluginManagementService pluginManagementService,
|
||||
IRgbService rgbService,
|
||||
IProfileService profileService,
|
||||
IModuleService moduleService // injected to ensure module priorities get applied
|
||||
)
|
||||
IModuleService moduleService)
|
||||
{
|
||||
Kernel = kernel;
|
||||
Constants.CorePlugin.Kernel = kernel;
|
||||
@@ -53,18 +51,14 @@ namespace Artemis.Core.Services
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_rgbService = rgbService;
|
||||
_profileService = profileService;
|
||||
_moduleService = moduleService;
|
||||
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
|
||||
_frameStopWatch = new Stopwatch();
|
||||
StartupArguments = new List<string>();
|
||||
|
||||
UpdatePluginCache();
|
||||
|
||||
|
||||
_rgbService.IsRenderPaused = true;
|
||||
_rgbService.Surface.Updating += SurfaceOnUpdating;
|
||||
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
|
||||
|
||||
_pluginManagementService.PluginFeatureEnabled += (sender, args) => UpdatePluginCache();
|
||||
_pluginManagementService.PluginFeatureDisabled += (sender, args) => UpdatePluginCache();
|
||||
}
|
||||
|
||||
// ReSharper restore UnusedParameter.Local
|
||||
@@ -79,12 +73,6 @@ namespace Artemis.Core.Services
|
||||
FrameRendered?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void UpdatePluginCache()
|
||||
{
|
||||
_modules = _pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).ToList();
|
||||
_dataModelExpansions = _pluginManagementService.GetFeaturesOfType<BaseDataModelExpansion>().Where(p => p.IsEnabled).ToList();
|
||||
}
|
||||
|
||||
private void ApplyLoggingLevel()
|
||||
{
|
||||
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
|
||||
@@ -119,68 +107,19 @@ namespace Artemis.Core.Services
|
||||
try
|
||||
{
|
||||
_frameStopWatch.Restart();
|
||||
|
||||
// Render all active modules
|
||||
|
||||
_moduleService.UpdateActiveModules(args.DeltaTime);
|
||||
SKTexture texture = _rgbService.OpenRender();
|
||||
|
||||
lock (_dataModelExpansions)
|
||||
{
|
||||
// Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated
|
||||
foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled))
|
||||
{
|
||||
try
|
||||
{
|
||||
dataModelExpansion.InternalUpdate(args.DeltaTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_updateExceptions.Add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Module> modules;
|
||||
lock (_modules)
|
||||
{
|
||||
modules = _modules.Where(m => m.IsActivated || m.InternalExpandsMainDataModel)
|
||||
.OrderBy(m => m.PriorityCategory)
|
||||
.ThenByDescending(m => m.Priority)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Update all active modules
|
||||
foreach (Module module in modules)
|
||||
{
|
||||
try
|
||||
{
|
||||
module.InternalUpdate(args.DeltaTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_updateExceptions.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
SKCanvas canvas = texture.Surface.Canvas;
|
||||
canvas.Save();
|
||||
if (Math.Abs(texture.RenderScale - 1) > 0.001)
|
||||
canvas.Scale(texture.RenderScale);
|
||||
canvas.Clear(new SKColor(0, 0, 0));
|
||||
|
||||
// While non-activated modules may be updated above if they expand the main data model, they may never render
|
||||
if (!ModuleRenderingDisabled)
|
||||
if (!ProfileRenderingDisabled)
|
||||
{
|
||||
foreach (Module module in modules.Where(m => m.IsActivated))
|
||||
{
|
||||
try
|
||||
{
|
||||
module.InternalRender(args.DeltaTime, canvas, texture.ImageInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_updateExceptions.Add(e);
|
||||
}
|
||||
}
|
||||
_profileService.UpdateProfiles(args.DeltaTime);
|
||||
_profileService.RenderProfiles(canvas);
|
||||
}
|
||||
|
||||
OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
|
||||
@@ -228,7 +167,7 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
public TimeSpan FrameTime { get; private set; }
|
||||
public bool ModuleRenderingDisabled { get; set; }
|
||||
public bool ProfileRenderingDisabled { get; set; }
|
||||
public List<string> StartupArguments { get; set; }
|
||||
public bool IsElevated { get; set; }
|
||||
|
||||
@@ -272,25 +211,6 @@ namespace Artemis.Core.Services
|
||||
OnInitialized();
|
||||
}
|
||||
|
||||
public void PlayIntroAnimation()
|
||||
{
|
||||
IntroAnimation intro = new(_logger, _profileService, _rgbService.EnabledDevices);
|
||||
|
||||
// Draw a white overlay over the device
|
||||
void DrawOverlay(object? sender, FrameRenderingEventArgs args)
|
||||
{
|
||||
if (intro.AnimationProfile.GetAllLayers().All(l => l.Timeline.IsFinished))
|
||||
{
|
||||
FrameRendering -= DrawOverlay;
|
||||
intro.AnimationProfile.Dispose();
|
||||
}
|
||||
|
||||
intro.Render(args.DeltaTime, args.Canvas);
|
||||
}
|
||||
|
||||
FrameRendering += DrawOverlay;
|
||||
}
|
||||
|
||||
public event EventHandler? Initialized;
|
||||
public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
|
||||
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace Artemis.Core.Services
|
||||
TimeSpan FrameTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether modules are rendered each frame by calling their Render method
|
||||
/// Gets or sets whether profiles are rendered each frame by calling their Render method
|
||||
/// </summary>
|
||||
bool ModuleRenderingDisabled { get; set; }
|
||||
bool ProfileRenderingDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of startup arguments
|
||||
@@ -38,11 +38,6 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// Plays the into animation profile defined in <c>Resources/intro-profile.json</c>
|
||||
/// </summary>
|
||||
void PlayIntroAnimation();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs the core has finished initializing
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Core.Modules;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
@@ -10,33 +10,29 @@ namespace Artemis.Core.Services
|
||||
public interface IModuleService : IArtemisService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current active module override. If set, all other modules are deactivated and only the
|
||||
/// <see cref="ActiveModuleOverride" /> is active.
|
||||
/// Updates all currently active modules
|
||||
/// </summary>
|
||||
Module? ActiveModuleOverride { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current <see cref="ActiveModuleOverride" /> and deactivates all other modules
|
||||
/// </summary>
|
||||
/// <param name="overrideModule"></param>
|
||||
Task SetActiveModuleOverride(Module? overrideModule);
|
||||
/// <param name="deltaTime"></param>
|
||||
void UpdateActiveModules(double deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates every enabled module's activation requirements and activates/deactivates modules accordingly
|
||||
/// </summary>
|
||||
Task UpdateModuleActivation();
|
||||
void UpdateModuleActivation();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the priority and priority category of the given module
|
||||
/// Overrides activation on the provided module and restores regular activation to any remaining modules
|
||||
/// </summary>
|
||||
/// <param name="module">The module to update</param>
|
||||
/// <param name="category">The new priority category of the module</param>
|
||||
/// <param name="priority">The new priority of the module</param>
|
||||
void UpdateModulePriority(Module module, ModulePriorityCategory category, int priority);
|
||||
void SetActivationOverride(Module? module);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the priority of a module is updated.
|
||||
/// Occurs whenever a module is activated
|
||||
/// </summary>
|
||||
event EventHandler? ModulePriorityUpdated;
|
||||
event EventHandler<ModuleEventArgs> ModuleActivated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs whenever a module is deactivated
|
||||
/// </summary>
|
||||
event EventHandler<ModuleEventArgs> ModuleDeactivated;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
internal class ModuleService : IModuleService
|
||||
{
|
||||
private static readonly SemaphoreSlim ActiveModuleSemaphore = new(1, 1);
|
||||
private readonly Timer _activationUpdateTimer;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IModuleRepository _moduleRepository;
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly List<Module> _modules;
|
||||
private readonly object _updateLock = new();
|
||||
|
||||
public ModuleService(ILogger logger, IModuleRepository moduleRepository, IProfileRepository profileRepository, IPluginManagementService pluginManagementService, IProfileService profileService)
|
||||
private Module? _activationOverride;
|
||||
|
||||
public ModuleService(ILogger logger, IPluginManagementService pluginManagementService, IProfileService profileService)
|
||||
{
|
||||
_logger = logger;
|
||||
_moduleRepository = moduleRepository;
|
||||
_profileRepository = profileRepository;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_profileService = profileService;
|
||||
_pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
|
||||
|
||||
Timer activationUpdateTimer = new(2000);
|
||||
activationUpdateTimer.Start();
|
||||
activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
|
||||
_activationUpdateTimer = new Timer(2000);
|
||||
_activationUpdateTimer.Start();
|
||||
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
|
||||
|
||||
foreach (Module module in _pluginManagementService.GetFeaturesOfType<Module>())
|
||||
InitialiseOrApplyPriority(module);
|
||||
pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureEnabled;
|
||||
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
|
||||
_modules = pluginManagementService.GetFeaturesOfType<Module>().ToList();
|
||||
foreach (Module module in _modules)
|
||||
ImportDefaultProfiles(module);
|
||||
}
|
||||
|
||||
private async void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
|
||||
protected virtual void OnModuleActivated(ModuleEventArgs e)
|
||||
{
|
||||
await UpdateModuleActivation();
|
||||
ModuleActivated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private async Task ActivateModule(Module module)
|
||||
protected virtual void OnModuleDeactivated(ModuleEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProfileModule? profileModule = module as ProfileModule;
|
||||
|
||||
if (profileModule != null && profileModule.DefaultProfiles.Any())
|
||||
{
|
||||
List<ProfileDescriptor> descriptors = _profileService.GetProfileDescriptors(profileModule);
|
||||
foreach (ProfileEntity defaultProfile in profileModule.DefaultProfiles)
|
||||
{
|
||||
if (descriptors.All(d => d.Id != defaultProfile.Id))
|
||||
_profileRepository.Add(defaultProfile);
|
||||
}
|
||||
}
|
||||
|
||||
module.Activate(false);
|
||||
|
||||
try
|
||||
{
|
||||
// If this is a profile module, activate the last active profile after module activation
|
||||
if (profileModule != null)
|
||||
await _profileService.ActivateLastProfileAnimated(profileModule);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, $"Failed to activate last profile on module {module}");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to activate module.", e), "Failed to activate module");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeactivateModule(Module module)
|
||||
{
|
||||
try
|
||||
{
|
||||
// If this is a profile module, animate profile disable
|
||||
// module.Deactivate would do the same but without animation
|
||||
if (module.IsActivated && module is ProfileModule profileModule)
|
||||
await profileModule.ChangeActiveProfileAnimated(null, Enumerable.Empty<ArtemisDevice>());
|
||||
|
||||
module.Deactivate(false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(new ArtemisPluginFeatureException(
|
||||
module, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile"
|
||||
);
|
||||
throw;
|
||||
}
|
||||
ModuleDeactivated?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void OverrideActivate(Module module)
|
||||
@@ -110,21 +55,15 @@ namespace Artemis.Core.Services
|
||||
// If activating while it should be deactivated, its an override
|
||||
bool shouldBeActivated = module.EvaluateActivationRequirements();
|
||||
module.Activate(!shouldBeActivated);
|
||||
|
||||
// If this is a profile module, activate the last active profile after module activation
|
||||
if (module is ProfileModule profileModule)
|
||||
_profileService.ActivateLastProfile(profileModule);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(new ArtemisPluginFeatureException(
|
||||
module, "Failed to activate module and last profile.", e), "Failed to activate module and last profile"
|
||||
);
|
||||
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to activate module.", e), "Failed to activate module");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OverrideDeactivate(Module module, bool clearingOverride)
|
||||
private void OverrideDeactivate(Module module)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -134,163 +73,132 @@ namespace Artemis.Core.Services
|
||||
// If deactivating while it should be activated, its an override
|
||||
bool shouldBeActivated = module.EvaluateActivationRequirements();
|
||||
// No need to deactivate if it is not in an overridden state
|
||||
if (shouldBeActivated && !module.IsActivatedOverride && !clearingOverride)
|
||||
if (shouldBeActivated && !module.IsActivatedOverride)
|
||||
return;
|
||||
|
||||
module.Deactivate(true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error(new ArtemisPluginFeatureException(
|
||||
module, "Failed to deactivate module and last profile.", e), "Failed to deactivate module and last profile"
|
||||
);
|
||||
_logger.Error(new ArtemisPluginFeatureException(module, "Failed to deactivate module.", e), "Failed to deactivate module");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
|
||||
private void ActivationUpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
if (e.PluginFeature is Module module)
|
||||
InitialiseOrApplyPriority(module);
|
||||
UpdateModuleActivation();
|
||||
}
|
||||
|
||||
private void InitialiseOrApplyPriority(Module module)
|
||||
private void PluginManagementServiceOnPluginFeatureEnabled(object? sender, PluginFeatureEventArgs e)
|
||||
{
|
||||
ModulePriorityCategory category = module.DefaultPriorityCategory;
|
||||
int priority = 1;
|
||||
|
||||
module.SettingsEntity = _moduleRepository.GetByModuleId(module.Id);
|
||||
if (module.SettingsEntity != null)
|
||||
lock (_updateLock)
|
||||
{
|
||||
category = (ModulePriorityCategory) module.SettingsEntity.PriorityCategory;
|
||||
priority = module.SettingsEntity.Priority;
|
||||
if (e.PluginFeature is Module module && !_modules.Contains(module))
|
||||
{
|
||||
ImportDefaultProfiles(module);
|
||||
_modules.Add(module);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateModulePriority(module, category, priority);
|
||||
}
|
||||
|
||||
public Module? ActiveModuleOverride { get; private set; }
|
||||
private void PluginManagementServiceOnPluginFeatureDisabled(object? sender, PluginFeatureEventArgs e)
|
||||
{
|
||||
lock (_updateLock)
|
||||
{
|
||||
if (e.PluginFeature is Module module)
|
||||
_modules.Remove(module);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetActiveModuleOverride(Module? overrideModule)
|
||||
private void ImportDefaultProfiles(Module module)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ActiveModuleSemaphore.WaitAsync();
|
||||
|
||||
if (ActiveModuleOverride == overrideModule)
|
||||
return;
|
||||
|
||||
if (overrideModule != null)
|
||||
List<ProfileConfiguration> profileConfigurations = _profileService.ProfileCategories.SelectMany(c => c.ProfileConfigurations).ToList();
|
||||
foreach ((DefaultCategoryName categoryName, string profilePath) in module.DefaultProfilePaths)
|
||||
{
|
||||
OverrideActivate(overrideModule);
|
||||
_logger.Information($"Setting active module override to {overrideModule.DisplayName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Information("Clearing active module override");
|
||||
}
|
||||
ProfileConfigurationExportModel? profileConfigurationExportModel = JsonConvert.DeserializeObject<ProfileConfigurationExportModel>(File.ReadAllText(profilePath), IProfileService.ExportSettings);
|
||||
if (profileConfigurationExportModel?.ProfileEntity == null)
|
||||
throw new ArtemisCoreException($"Default profile at path {profilePath} contains no valid profile data");
|
||||
if (profileConfigurations.Any(p => p.Entity.ProfileId == profileConfigurationExportModel.ProfileEntity.Id))
|
||||
continue;
|
||||
|
||||
// Always deactivate all other modules whenever override is called
|
||||
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>().ToList();
|
||||
foreach (Module module in modules.Where(m => m != overrideModule))
|
||||
OverrideDeactivate(module, overrideModule != null);
|
||||
ProfileCategory category = _profileService.ProfileCategories.FirstOrDefault(c => c.Name == categoryName.ToString()) ??
|
||||
_profileService.CreateProfileCategory(categoryName.ToString());
|
||||
|
||||
ActiveModuleOverride = overrideModule;
|
||||
_profileService.ImportProfile(category, profileConfigurationExportModel, false, true, null);
|
||||
}
|
||||
}
|
||||
finally
|
||||
catch (Exception e)
|
||||
{
|
||||
ActiveModuleSemaphore.Release();
|
||||
_logger.Warning(e, "Failed to import default profiles for module {module}", module);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateModuleActivation()
|
||||
public void UpdateModuleActivation()
|
||||
{
|
||||
if (ActiveModuleSemaphore.CurrentCount == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
lock (_updateLock)
|
||||
{
|
||||
await ActiveModuleSemaphore.WaitAsync();
|
||||
|
||||
if (ActiveModuleOverride != null)
|
||||
try
|
||||
{
|
||||
// The conditions of the active module override may be matched, in that case reactivate as a non-override
|
||||
// the principle is different for this service but not for the module
|
||||
bool shouldBeActivated = ActiveModuleOverride.EvaluateActivationRequirements();
|
||||
if (shouldBeActivated && ActiveModuleOverride.IsActivatedOverride)
|
||||
ActiveModuleOverride.Reactivate(true, false);
|
||||
else if (!shouldBeActivated && !ActiveModuleOverride.IsActivatedOverride) ActiveModuleOverride.Reactivate(false, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
List<Module> modules = _pluginManagementService.GetFeaturesOfType<Module>().ToList();
|
||||
List<Task> tasks = new();
|
||||
foreach (Module module in modules)
|
||||
{
|
||||
lock (module)
|
||||
_activationUpdateTimer.Elapsed -= ActivationUpdateTimerOnElapsed;
|
||||
foreach (Module module in _modules)
|
||||
{
|
||||
bool shouldBeActivated = module.EvaluateActivationRequirements() && module.IsEnabled;
|
||||
if (module.IsActivatedOverride)
|
||||
continue;
|
||||
|
||||
if (module.IsAlwaysAvailable)
|
||||
{
|
||||
module.Activate(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
module.Profiler.StartMeasurement("EvaluateActivationRequirements");
|
||||
bool shouldBeActivated = module.IsEnabled && module.EvaluateActivationRequirements();
|
||||
module.Profiler.StopMeasurement("EvaluateActivationRequirements");
|
||||
|
||||
if (shouldBeActivated && !module.IsActivated)
|
||||
tasks.Add(ActivateModule(module));
|
||||
{
|
||||
module.Activate(false);
|
||||
OnModuleActivated(new ModuleEventArgs(module));
|
||||
}
|
||||
else if (!shouldBeActivated && module.IsActivated)
|
||||
tasks.Add(DeactivateModule(module));
|
||||
{
|
||||
module.Deactivate(false);
|
||||
OnModuleDeactivated(new ModuleEventArgs(module));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
|
||||
stopwatch.Stop();
|
||||
if (stopwatch.ElapsedMilliseconds > 100 && !tasks.Any())
|
||||
_logger.Warning("Activation requirements evaluation took too long: {moduleCount} module(s) in {elapsed}", modules.Count, stopwatch.Elapsed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ActiveModuleSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateModulePriority(Module module, ModulePriorityCategory category, int priority)
|
||||
{
|
||||
if (module.PriorityCategory == category && module.Priority == priority)
|
||||
return;
|
||||
|
||||
List<Module> modules = _pluginManagementService
|
||||
.GetFeaturesOfType<Module>()
|
||||
.Where(m => m.PriorityCategory == category)
|
||||
.OrderBy(m => m.Priority)
|
||||
.ToList();
|
||||
|
||||
if (modules.Contains(module))
|
||||
modules.Remove(module);
|
||||
|
||||
priority = Math.Min(modules.Count, Math.Max(0, priority));
|
||||
modules.Insert(priority, module);
|
||||
|
||||
module.PriorityCategory = category;
|
||||
for (int index = 0; index < modules.Count; index++)
|
||||
{
|
||||
Module categoryModule = modules[index];
|
||||
categoryModule.Priority = index;
|
||||
|
||||
// Don't save modules whose priority hasn't been initialized yet
|
||||
if (categoryModule == module || categoryModule.SettingsEntity != null)
|
||||
finally
|
||||
{
|
||||
categoryModule.ApplyToEntity();
|
||||
_moduleRepository.Save(categoryModule.SettingsEntity);
|
||||
_activationUpdateTimer.Elapsed += ActivationUpdateTimerOnElapsed;
|
||||
}
|
||||
}
|
||||
|
||||
ModulePriorityUpdated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#region Events
|
||||
public void SetActivationOverride(Module? module)
|
||||
{
|
||||
lock (_updateLock)
|
||||
{
|
||||
if (_activationOverride != null)
|
||||
OverrideDeactivate(_activationOverride);
|
||||
_activationOverride = module;
|
||||
if (_activationOverride != null)
|
||||
OverrideActivate(_activationOverride);
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler? ModulePriorityUpdated;
|
||||
public void UpdateActiveModules(double deltaTime)
|
||||
{
|
||||
lock (_updateLock)
|
||||
{
|
||||
foreach (Module module in _modules)
|
||||
module.InternalUpdate(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
public event EventHandler<ModuleEventArgs>? ModuleActivated;
|
||||
public event EventHandler<ModuleEventArgs>? ModuleDeactivated;
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,6 @@ namespace Artemis.Core.Services
|
||||
// Add data models of already loaded plugins
|
||||
foreach (Module module in pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled))
|
||||
AddModuleDataModel(module);
|
||||
foreach (BaseDataModelExpansion dataModelExpansion in pluginManagementService.GetFeaturesOfType<BaseDataModelExpansion>().Where(p => p.IsEnabled))
|
||||
AddDataModelExpansionDataModel(dataModelExpansion);
|
||||
|
||||
// Add data models of new plugins when they get enabled
|
||||
pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
|
||||
@@ -54,8 +52,6 @@ namespace Artemis.Core.Services
|
||||
{
|
||||
if (e.PluginFeature is Module module)
|
||||
AddModuleDataModel(module);
|
||||
else if (e.PluginFeature is BaseDataModelExpansion dataModelExpansion)
|
||||
AddDataModelExpansionDataModel(dataModelExpansion);
|
||||
}
|
||||
|
||||
private void AddModuleDataModel(Module module)
|
||||
@@ -66,19 +62,8 @@ namespace Artemis.Core.Services
|
||||
if (module.InternalDataModel.DataModelDescription == null)
|
||||
throw new ArtemisPluginFeatureException(module, "Module overrides GetDataModelDescription but returned null");
|
||||
|
||||
module.InternalDataModel.IsExpansion = module.InternalExpandsMainDataModel;
|
||||
module.InternalDataModel.IsExpansion = module.IsAlwaysAvailable;
|
||||
RegisterDataModel(module.InternalDataModel);
|
||||
}
|
||||
|
||||
private void AddDataModelExpansionDataModel(BaseDataModelExpansion dataModelExpansion)
|
||||
{
|
||||
if (dataModelExpansion.InternalDataModel == null)
|
||||
throw new ArtemisCoreException("Cannot add data model expansion that is not enabled");
|
||||
if (dataModelExpansion.InternalDataModel.DataModelDescription == null)
|
||||
throw new ArtemisPluginFeatureException(dataModelExpansion, "Data model expansion overrides GetDataModelDescription but returned null");
|
||||
|
||||
dataModelExpansion.InternalDataModel.IsExpansion = true;
|
||||
RegisterDataModel(dataModelExpansion.InternalDataModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.Modules;
|
||||
using System.Collections.ObjectModel;
|
||||
using Newtonsoft.Json;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
@@ -10,122 +10,144 @@ namespace Artemis.Core.Services
|
||||
public interface IProfileService : IArtemisService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new profile for the given module and returns a descriptor pointing to it
|
||||
/// Gets the JSON serializer settings used to create profile mementos
|
||||
/// </summary>
|
||||
/// <param name="module">The profile module to create the profile for</param>
|
||||
/// <param name="name">The name of the new profile</param>
|
||||
/// <returns></returns>
|
||||
ProfileDescriptor CreateProfileDescriptor(ProfileModule module, string name);
|
||||
public static JsonSerializerSettings MementoSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All};
|
||||
|
||||
/// <summary>
|
||||
/// Gets a descriptor for each profile stored for the given <see cref="ProfileModule" />
|
||||
/// Gets the JSON serializer settings used to import/export profiles
|
||||
/// </summary>
|
||||
/// <param name="module">The module to return profile descriptors for</param>
|
||||
/// <returns></returns>
|
||||
List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module);
|
||||
public static JsonSerializerSettings ExportSettings { get; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection containing all the profile categories
|
||||
/// </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 a boolean indicating whether rendering should only be done for profiles being edited
|
||||
/// </summary>
|
||||
bool RenderForEditor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
|
||||
/// </summary>
|
||||
/// <param name="profileConfiguration">The profile configuration of the profile to activate</param>
|
||||
Profile ActivateProfile(ProfileConfiguration profileConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface
|
||||
/// </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
|
||||
/// </summary>
|
||||
/// <param name="profileCategory">The profile category to update</param>
|
||||
void SaveProfileCategory(ProfileCategory profileCategory);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new profile category and saves it to persistent storage
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the new profile category, must be unique</param>
|
||||
/// <returns>The newly created profile category</returns>
|
||||
ProfileCategory CreateProfileCategory(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Permanently deletes the provided profile category
|
||||
/// </summary>
|
||||
void DeleteProfileCategory(ProfileCategory profileCategory);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
/// <param name="category">The profile category to add the profile to</param>
|
||||
/// <param name="name">The name of the new profile configuration</param>
|
||||
/// <param name="icon">The icon of the new profile configuration</param>
|
||||
/// <returns>The newly created profile configuration</returns>
|
||||
ProfileConfiguration CreateProfileConfiguration(ProfileCategory category, string name, string icon);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided profile configuration from the <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
/// <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>
|
||||
/// <param name="profile"></param>
|
||||
/// <param name="includeChildren"></param>
|
||||
void UpdateProfile(Profile profile, bool includeChildren);
|
||||
void SaveProfile(Profile profile, bool includeChildren);
|
||||
|
||||
/// <summary>
|
||||
/// Disposes and permanently deletes the provided profile
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile to delete</param>
|
||||
void DeleteProfile(Profile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Permanently deletes the profile described by the provided profile descriptor
|
||||
/// </summary>
|
||||
/// <param name="profileDescriptor">The descriptor pointing to the profile to delete</param>
|
||||
void DeleteProfile(ProfileDescriptor profileDescriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Activates the last profile of the given profile module
|
||||
/// </summary>
|
||||
/// <param name="profileModule"></param>
|
||||
void ActivateLastProfile(ProfileModule profileModule);
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the currently active profile on the provided profile module
|
||||
/// </summary>
|
||||
void ReloadProfile(ProfileModule module);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously activates the last profile of the given profile module using a fade animation
|
||||
/// </summary>
|
||||
/// <param name="profileModule"></param>
|
||||
/// <returns></returns>
|
||||
Task ActivateLastProfileAnimated(ProfileModule profileModule);
|
||||
|
||||
/// <summary>
|
||||
/// Activates the profile described in the given <see cref="ProfileDescriptor" /> with the currently active surface
|
||||
/// </summary>
|
||||
/// <param name="profileDescriptor">The descriptor describing the profile to activate</param>
|
||||
Profile ActivateProfile(ProfileDescriptor profileDescriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously activates the profile described in the given <see cref="ProfileDescriptor" /> with the currently
|
||||
/// active surface using a fade animation
|
||||
/// </summary>
|
||||
/// <param name="profileDescriptor">The descriptor describing the profile to activate</param>
|
||||
Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the active profile on the given <see cref="ProfileModule" />
|
||||
/// </summary>
|
||||
/// <param name="module">The profile module to deactivate the active profile on</param>
|
||||
void ClearActiveProfile(ProfileModule module);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously clears the active profile on the given <see cref="ProfileModule" /> using a fade animation
|
||||
/// </summary>
|
||||
/// <param name="module">The profile module to deactivate the active profile on</param>
|
||||
Task ClearActiveProfileAnimated(ProfileModule module);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to restore the profile to the state it had before the last <see cref="UpdateProfile" /> call.
|
||||
/// Attempts to restore the profile to the state it had before the last <see cref="SaveProfile" /> call.
|
||||
/// </summary>
|
||||
/// <param name="profile"></param>
|
||||
bool UndoUpdateProfile(Profile profile);
|
||||
bool UndoSaveProfile(Profile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to restore the profile to the state it had before the last <see cref="UndoUpdateProfile" /> call.
|
||||
/// Attempts to restore the profile to the state it had before the last <see cref="UndoSaveProfile" /> call.
|
||||
/// </summary>
|
||||
/// <param name="profile"></param>
|
||||
bool RedoUpdateProfile(Profile profile);
|
||||
bool RedoSaveProfile(Profile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the profile for rendering. You should not need to call this, it is exposed for some niche usage in the
|
||||
/// core
|
||||
/// Exports the profile described in the given <see cref="ProfileConfiguration" /> into an export model
|
||||
/// </summary>
|
||||
/// <param name="profile"></param>
|
||||
void InstantiateProfile(Profile profile);
|
||||
/// <param name="profileConfiguration">The profile configuration of the profile to export</param>
|
||||
/// <returns>The resulting export model</returns>
|
||||
ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// [Placeholder] Exports the profile described in the given <see cref="ProfileDescriptor" /> in a JSON format
|
||||
/// Imports the provided base64 encoded GZIPed JSON as a profile configuration
|
||||
/// </summary>
|
||||
/// <param name="profileDescriptor">The descriptor of the profile to export</param>
|
||||
/// <returns>The resulting JSON</returns>
|
||||
string ExportProfile(ProfileDescriptor profileDescriptor);
|
||||
|
||||
/// <summary>
|
||||
/// [Placeholder] Imports the provided base64 encoded GZIPed JSON as a profile for the given
|
||||
/// <see cref="ProfileModule" />
|
||||
/// </summary>
|
||||
/// <param name="json">The content of the profile as JSON</param>
|
||||
/// <param name="profileModule">The module to import the profile in to</param>
|
||||
/// <param name="category">The <see cref="ProfileCategory" /> in which to import the profile</param>
|
||||
/// <param name="exportModel">The model containing the profile to import</param>
|
||||
/// <param name="makeUnique">Whether or not to give the profile a new GUID, making it unique</param>
|
||||
/// <param name="markAsFreshImport">Whether or not to mark the profile as a fresh import, causing it to be adapted until any changes are made to it</param>
|
||||
/// <param name="nameAffix">Text to add after the name of the profile (separated by a dash)</param>
|
||||
/// <returns></returns>
|
||||
ProfileDescriptor ImportProfile(string json, ProfileModule profileModule, string nameAffix = "imported");
|
||||
/// <returns>The resulting profile configuration</returns>
|
||||
ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique = true, bool markAsFreshImport = true, string? nameAffix = "imported");
|
||||
|
||||
/// <summary>
|
||||
/// Adapts a given profile to the currently active devices
|
||||
/// </summary>
|
||||
/// <param name="profile">The profile to adapt</param>
|
||||
void AdaptProfile(Profile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Updates all currently active profiles
|
||||
/// </summary>
|
||||
void UpdateProfiles(double deltaTime);
|
||||
|
||||
/// <summary>
|
||||
/// Renders all currently active profiles
|
||||
/// </summary>
|
||||
/// <param name="canvas"></param>
|
||||
void RenderProfiles(SKCanvas canvas);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.Core.Services
|
||||
{
|
||||
@@ -14,48 +15,38 @@ namespace Artemis.Core.Services
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly List<ProfileCategory> _profileCategories;
|
||||
private readonly IProfileCategoryRepository _profileCategoryRepository;
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly IRgbService _rgbService;
|
||||
|
||||
private readonly List<Exception> _updateExceptions = new();
|
||||
private DateTime _lastUpdateExceptionLog;
|
||||
private readonly List<Exception> _renderExceptions = new();
|
||||
private DateTime _lastRenderExceptionLog;
|
||||
|
||||
public ProfileService(ILogger logger,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IRgbService rgbService,
|
||||
// TODO: Move these two
|
||||
IConditionOperatorService conditionOperatorService,
|
||||
IDataBindingService dataBindingService,
|
||||
IProfileCategoryRepository profileCategoryRepository,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IProfileRepository profileRepository)
|
||||
{
|
||||
_logger = logger;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_rgbService = rgbService;
|
||||
_profileCategoryRepository = profileCategoryRepository;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_profileRepository = profileRepository;
|
||||
_profileCategories = new List<ProfileCategory>(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
|
||||
|
||||
_rgbService.LedsChanged += RgbServiceOnLedsChanged;
|
||||
}
|
||||
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
|
||||
|
||||
public static JsonSerializerSettings MementoSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All};
|
||||
public static JsonSerializerSettings ExportSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
|
||||
|
||||
public ProfileDescriptor? GetLastActiveProfile(ProfileModule module)
|
||||
{
|
||||
List<ProfileEntity> moduleProfiles = _profileRepository.GetByModuleId(module.Id);
|
||||
if (!moduleProfiles.Any())
|
||||
return CreateProfileDescriptor(module, "Default");
|
||||
|
||||
ProfileEntity? profileEntity = moduleProfiles.FirstOrDefault(p => p.IsActive) ?? moduleProfiles.FirstOrDefault();
|
||||
return profileEntity == null ? null : new ProfileDescriptor(module, profileEntity);
|
||||
}
|
||||
|
||||
private void SaveActiveProfile(ProfileModule module)
|
||||
{
|
||||
if (module.ActiveProfile == null)
|
||||
return;
|
||||
|
||||
List<ProfileEntity> profileEntities = _profileRepository.GetByModuleId(module.Id);
|
||||
foreach (ProfileEntity profileEntity in profileEntities)
|
||||
{
|
||||
profileEntity.IsActive = module.ActiveProfile.EntityId == profileEntity.Id;
|
||||
_profileRepository.Save(profileEntity);
|
||||
}
|
||||
if (!_profileCategories.Any())
|
||||
CreateDefaultProfileCategories();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,168 +54,324 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
private void ActiveProfilesPopulateLeds()
|
||||
{
|
||||
List<ProfileModule> profileModules = _pluginManagementService.GetFeaturesOfType<ProfileModule>();
|
||||
foreach (ProfileModule profileModule in profileModules)
|
||||
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
|
||||
{
|
||||
// Avoid race condition, make the check here
|
||||
if (profileModule.ActiveProfile == null)
|
||||
continue;
|
||||
|
||||
profileModule.ActiveProfile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
if (profileModule.ActiveProfile.IsFreshImport)
|
||||
if (profileConfiguration.Profile == null) continue;
|
||||
profileConfiguration.Profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
|
||||
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)
|
||||
foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
|
||||
profileConfiguration.LoadModules(modules);
|
||||
}
|
||||
}
|
||||
|
||||
private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
|
||||
{
|
||||
ActiveProfilesPopulateLeds();
|
||||
}
|
||||
|
||||
private void PluginManagementServiceOnPluginFeatureToggled(object? sender, PluginFeatureEventArgs e)
|
||||
{
|
||||
if (e.PluginFeature is Module)
|
||||
UpdateModules();
|
||||
}
|
||||
|
||||
public bool RenderForEditor { get; set; }
|
||||
|
||||
public void UpdateProfiles(double deltaTime)
|
||||
{
|
||||
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--)
|
||||
{
|
||||
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileModule.ActiveProfile);
|
||||
AdaptProfile(profileModule.ActiveProfile);
|
||||
ProfileCategory profileCategory = _profileCategories[i];
|
||||
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||
{
|
||||
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||
// Profiles being edited are updated at their own leisure
|
||||
if (profileConfiguration.IsBeingEdited)
|
||||
continue;
|
||||
|
||||
bool shouldBeActive = profileConfiguration.ShouldBeActive(false);
|
||||
if (shouldBeActive)
|
||||
{
|
||||
profileConfiguration.Update();
|
||||
shouldBeActive = profileConfiguration.ActivationConditionMet;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Make sure the profile is active or inactive according to the parameters above
|
||||
if (shouldBeActive && profileConfiguration.Profile == null)
|
||||
ActivateProfile(profileConfiguration);
|
||||
else if (!shouldBeActive && profileConfiguration.Profile != null)
|
||||
DeactivateProfile(profileConfiguration);
|
||||
|
||||
profileConfiguration.Profile?.Update(deltaTime);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_updateExceptions.Add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogProfileUpdateExceptions();
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderProfiles(SKCanvas canvas)
|
||||
{
|
||||
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--)
|
||||
{
|
||||
ProfileCategory profileCategory = _profileCategories[i];
|
||||
for (int j = profileCategory.ProfileConfigurations.Count - 1; j > -1; j--)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
|
||||
if (RenderForEditor)
|
||||
{
|
||||
if (profileConfiguration.IsBeingEdited)
|
||||
profileConfiguration.Profile?.Render(canvas, SKPointI.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure all criteria are met before rendering
|
||||
if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet)
|
||||
profileConfiguration.Profile?.Render(canvas, SKPointI.Empty);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_renderExceptions.Add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogProfileRenderExceptions();
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateDefaultProfileCategories()
|
||||
{
|
||||
foreach (DefaultCategoryName defaultCategoryName in Enum.GetValues<DefaultCategoryName>())
|
||||
CreateProfileCategory(defaultCategoryName.ToString());
|
||||
}
|
||||
|
||||
private void LogProfileUpdateExceptions()
|
||||
{
|
||||
// Only log update exceptions every 10 seconds to avoid spamming the logs
|
||||
if (DateTime.Now - _lastUpdateExceptionLog < TimeSpan.FromSeconds(10))
|
||||
return;
|
||||
_lastUpdateExceptionLog = DateTime.Now;
|
||||
|
||||
if (!_updateExceptions.Any())
|
||||
return;
|
||||
|
||||
// Group by stack trace, that should gather up duplicate exceptions
|
||||
foreach (IGrouping<string?, Exception> exceptions in _updateExceptions.GroupBy(e => e.StackTrace))
|
||||
_logger.Warning(exceptions.First(), "Exception was thrown {count} times during profile update in the last 10 seconds", exceptions.Count());
|
||||
|
||||
// When logging is finished start with a fresh slate
|
||||
_updateExceptions.Clear();
|
||||
}
|
||||
|
||||
private void LogProfileRenderExceptions()
|
||||
{
|
||||
// Only log update exceptions every 10 seconds to avoid spamming the logs
|
||||
if (DateTime.Now - _lastRenderExceptionLog < TimeSpan.FromSeconds(10))
|
||||
return;
|
||||
_lastRenderExceptionLog = DateTime.Now;
|
||||
|
||||
if (!_renderExceptions.Any())
|
||||
return;
|
||||
|
||||
// Group by stack trace, that should gather up duplicate exceptions
|
||||
foreach (IGrouping<string?, Exception> exceptions in _renderExceptions.GroupBy(e => e.StackTrace))
|
||||
_logger.Warning(exceptions.First(), "Exception was thrown {count} times during profile render in the last 10 seconds", exceptions.Count());
|
||||
|
||||
// When logging is finished start with a fresh slate
|
||||
_renderExceptions.Clear();
|
||||
}
|
||||
|
||||
public ReadOnlyCollection<ProfileCategory> ProfileCategories
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_profileRepository)
|
||||
{
|
||||
return _profileCategories.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module)
|
||||
public ReadOnlyCollection<ProfileConfiguration> ProfileConfigurations
|
||||
{
|
||||
List<ProfileEntity> profileEntities = _profileRepository.GetByModuleId(module.Id);
|
||||
return profileEntities.Select(e => new ProfileDescriptor(module, e)).ToList();
|
||||
get
|
||||
{
|
||||
lock (_profileRepository)
|
||||
{
|
||||
return _profileCategories.SelectMany(c => c.ProfileConfigurations).ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ProfileDescriptor CreateProfileDescriptor(ProfileModule module, string name)
|
||||
public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileEntity profileEntity = new() {Id = Guid.NewGuid(), Name = name, ModuleId = module.Id};
|
||||
_profileRepository.Add(profileEntity);
|
||||
if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
return;
|
||||
if (profileConfiguration.Icon.FileIcon != null)
|
||||
return;
|
||||
|
||||
return new ProfileDescriptor(module, profileEntity);
|
||||
profileConfiguration.Icon.FileIcon = _profileCategoryRepository.GetProfileIconStream(profileConfiguration.Entity.FileIconId);
|
||||
}
|
||||
|
||||
public void ActivateLastProfile(ProfileModule profileModule)
|
||||
public void SaveProfileConfigurationIcon(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileDescriptor? activeProfile = GetLastActiveProfile(profileModule);
|
||||
if (activeProfile != null)
|
||||
ActivateProfile(activeProfile);
|
||||
if (profileConfiguration.Icon.IconType == ProfileConfigurationIconType.MaterialIcon)
|
||||
return;
|
||||
|
||||
if (profileConfiguration.Icon.FileIcon != null)
|
||||
{
|
||||
profileConfiguration.Icon.FileIcon.Position = 0;
|
||||
_profileCategoryRepository.SaveProfileIconStream(profileConfiguration.Entity, profileConfiguration.Icon.FileIcon);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ActivateLastProfileAnimated(ProfileModule profileModule)
|
||||
public Profile ActivateProfile(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
ProfileDescriptor? activeProfile = GetLastActiveProfile(profileModule);
|
||||
if (activeProfile != null)
|
||||
await ActivateProfileAnimated(activeProfile);
|
||||
}
|
||||
if (profileConfiguration.Profile != null)
|
||||
return profileConfiguration.Profile;
|
||||
|
||||
public Profile ActivateProfile(ProfileDescriptor profileDescriptor)
|
||||
{
|
||||
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
|
||||
return profileDescriptor.ProfileModule.ActiveProfile;
|
||||
|
||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
||||
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisCoreException($"Cannot find profile named: {profileDescriptor.Name} ID: {profileDescriptor.Id}");
|
||||
throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}");
|
||||
|
||||
Profile profile = new(profileDescriptor.ProfileModule, profileEntity);
|
||||
InstantiateProfile(profile);
|
||||
Profile profile = new(profileConfiguration, profileEntity);
|
||||
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
|
||||
profileDescriptor.ProfileModule.ChangeActiveProfile(profile, _rgbService.EnabledDevices);
|
||||
if (profile.IsFreshImport)
|
||||
{
|
||||
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profile);
|
||||
AdaptProfile(profile);
|
||||
}
|
||||
|
||||
SaveActiveProfile(profileDescriptor.ProfileModule);
|
||||
|
||||
profileConfiguration.Profile = profile;
|
||||
return profile;
|
||||
}
|
||||
|
||||
public void ReloadProfile(ProfileModule module)
|
||||
public void DeactivateProfile(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
if (module.ActiveProfile == null)
|
||||
if (profileConfiguration.IsBeingEdited)
|
||||
throw new ArtemisCoreException("Cannot disable a profile that is being edited, that's rude");
|
||||
if (profileConfiguration.Profile == null)
|
||||
return;
|
||||
|
||||
ProfileEntity entity = _profileRepository.Get(module.ActiveProfile.EntityId);
|
||||
Profile profile = new(module, entity);
|
||||
InstantiateProfile(profile);
|
||||
|
||||
module.ChangeActiveProfile(null, _rgbService.EnabledDevices);
|
||||
module.ChangeActiveProfile(profile, _rgbService.EnabledDevices);
|
||||
Profile profile = profileConfiguration.Profile;
|
||||
profileConfiguration.Profile = null;
|
||||
profile.Dispose();
|
||||
}
|
||||
|
||||
public async Task<Profile> ActivateProfileAnimated(ProfileDescriptor profileDescriptor)
|
||||
public void DeleteProfile(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
if (profileDescriptor.ProfileModule.ActiveProfile?.EntityId == profileDescriptor.Id)
|
||||
return profileDescriptor.ProfileModule.ActiveProfile;
|
||||
DeactivateProfile(profileConfiguration);
|
||||
|
||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisCoreException($"Cannot find profile named: {profileDescriptor.Name} ID: {profileDescriptor.Id}");
|
||||
|
||||
Profile profile = new(profileDescriptor.ProfileModule, profileEntity);
|
||||
InstantiateProfile(profile);
|
||||
|
||||
void ActivatingRgbServiceOnLedsChanged(object? sender, EventArgs e)
|
||||
{
|
||||
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
}
|
||||
|
||||
void ActivatingProfilePluginToggle(object? sender, PluginEventArgs e)
|
||||
{
|
||||
if (!profile.Disposed)
|
||||
InstantiateProfile(profile);
|
||||
}
|
||||
|
||||
// This could happen during activation so subscribe to it
|
||||
_pluginManagementService.PluginEnabled += ActivatingProfilePluginToggle;
|
||||
_pluginManagementService.PluginDisabled += ActivatingProfilePluginToggle;
|
||||
_rgbService.LedsChanged += ActivatingRgbServiceOnLedsChanged;
|
||||
|
||||
await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _rgbService.EnabledDevices);
|
||||
if (profile.IsFreshImport)
|
||||
{
|
||||
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profile);
|
||||
AdaptProfile(profile);
|
||||
}
|
||||
|
||||
SaveActiveProfile(profileDescriptor.ProfileModule);
|
||||
|
||||
_pluginManagementService.PluginEnabled -= ActivatingProfilePluginToggle;
|
||||
_pluginManagementService.PluginDisabled -= ActivatingProfilePluginToggle;
|
||||
_rgbService.LedsChanged -= ActivatingRgbServiceOnLedsChanged;
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
|
||||
public void ClearActiveProfile(ProfileModule module)
|
||||
{
|
||||
module.ChangeActiveProfile(null, _rgbService.EnabledDevices);
|
||||
SaveActiveProfile(module);
|
||||
}
|
||||
|
||||
public async Task ClearActiveProfileAnimated(ProfileModule module)
|
||||
{
|
||||
await module.ChangeActiveProfileAnimated(null, _rgbService.EnabledDevices);
|
||||
}
|
||||
|
||||
public void DeleteProfile(Profile profile)
|
||||
{
|
||||
_logger.Debug("Removing profile " + profile);
|
||||
|
||||
// If the given profile is currently active, disable it first (this also disposes it)
|
||||
if (profile.Module.ActiveProfile == profile)
|
||||
ClearActiveProfile(profile.Module);
|
||||
else
|
||||
profile.Dispose();
|
||||
|
||||
_profileRepository.Remove(profile.ProfileEntity);
|
||||
}
|
||||
|
||||
public void DeleteProfile(ProfileDescriptor profileDescriptor)
|
||||
{
|
||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
||||
ProfileEntity profileEntity = _profileRepository.Get(profileConfiguration.Entity.ProfileId);
|
||||
if (profileEntity == null)
|
||||
return;
|
||||
|
||||
profileConfiguration.Category.RemoveProfileConfiguration(profileConfiguration);
|
||||
_profileRepository.Remove(profileEntity);
|
||||
SaveProfileCategory(profileConfiguration.Category);
|
||||
}
|
||||
|
||||
public void UpdateProfile(Profile profile, bool includeChildren)
|
||||
public ProfileCategory CreateProfileCategory(string name)
|
||||
{
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
lock (_profileRepository)
|
||||
{
|
||||
ProfileCategory profileCategory = new(name);
|
||||
_profileCategories.Add(profileCategory);
|
||||
SaveProfileCategory(profileCategory);
|
||||
return profileCategory;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteProfileCategory(ProfileCategory profileCategory)
|
||||
{
|
||||
List<ProfileConfiguration> profileConfigurations = profileCategory.ProfileConfigurations.ToList();
|
||||
foreach (ProfileConfiguration profileConfiguration in profileConfigurations)
|
||||
RemoveProfileConfiguration(profileConfiguration);
|
||||
|
||||
lock (_profileRepository)
|
||||
{
|
||||
_profileCategories.Remove(profileCategory);
|
||||
_profileCategoryRepository.Remove(profileCategory.Entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new profile configuration and adds it to the provided <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
/// <param name="category">The profile category to add the profile to</param>
|
||||
/// <param name="name">The name of the new profile configuration</param>
|
||||
/// <param name="icon">The icon of the new profile configuration</param>
|
||||
/// <returns>The newly created profile configuration</returns>
|
||||
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);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided profile configuration from the <see cref="ProfileCategory" />
|
||||
/// </summary>
|
||||
/// <param name="profileConfiguration"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
public void SaveProfileCategory(ProfileCategory profileCategory)
|
||||
{
|
||||
profileCategory.Save();
|
||||
_profileCategoryRepository.Save(profileCategory.Entity);
|
||||
|
||||
lock (_profileCategories)
|
||||
{
|
||||
_profileCategories.Sort((a, b) => a.Order - b.Order);
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveProfile(Profile profile, bool includeChildren)
|
||||
{
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||
profile.Save();
|
||||
if (includeChildren)
|
||||
{
|
||||
@@ -235,7 +382,7 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
// If there are no changes, don't bother saving
|
||||
string updatedMemento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
string updatedMemento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||
if (memento.Equals(updatedMemento))
|
||||
{
|
||||
_logger.Debug("Updating profile - Skipping save, no changes");
|
||||
@@ -253,7 +400,7 @@ namespace Artemis.Core.Services
|
||||
_profileRepository.Save(profile.ProfileEntity);
|
||||
}
|
||||
|
||||
public bool UndoUpdateProfile(Profile profile)
|
||||
public bool UndoSaveProfile(Profile profile)
|
||||
{
|
||||
// Keep the profile from being rendered by locking it
|
||||
lock (profile)
|
||||
@@ -265,20 +412,20 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
string top = profile.UndoStack.Pop();
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||
profile.RedoStack.Push(memento);
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings)
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, IProfileService.MementoSettings)
|
||||
?? throw new InvalidOperationException("Failed to deserialize memento");
|
||||
|
||||
profile.Load();
|
||||
InstantiateProfile(profile);
|
||||
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
}
|
||||
|
||||
_logger.Debug("Undo profile update - Success");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RedoUpdateProfile(Profile profile)
|
||||
public bool RedoSaveProfile(Profile profile)
|
||||
{
|
||||
// Keep the profile from being rendered by locking it
|
||||
lock (profile)
|
||||
@@ -290,53 +437,81 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
string top = profile.RedoStack.Pop();
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||
profile.UndoStack.Push(memento);
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, MementoSettings)
|
||||
profile.ProfileEntity = JsonConvert.DeserializeObject<ProfileEntity>(top, IProfileService.MementoSettings)
|
||||
?? throw new InvalidOperationException("Failed to deserialize memento");
|
||||
|
||||
profile.Load();
|
||||
InstantiateProfile(profile);
|
||||
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
|
||||
_logger.Debug("Redo profile update - Success");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void InstantiateProfile(Profile profile)
|
||||
public ProfileConfigurationExportModel ExportProfile(ProfileConfiguration profileConfiguration)
|
||||
{
|
||||
profile.PopulateLeds(_rgbService.EnabledDevices);
|
||||
// The profile may not be active and in that case lets activate it real quick
|
||||
Profile profile = profileConfiguration.Profile ?? ActivateProfile(profileConfiguration);
|
||||
|
||||
return new ProfileConfigurationExportModel
|
||||
{
|
||||
ProfileConfigurationEntity = profileConfiguration.Entity,
|
||||
ProfileEntity = profile.ProfileEntity,
|
||||
ProfileImage = profileConfiguration.Icon.FileIcon
|
||||
};
|
||||
}
|
||||
|
||||
public string ExportProfile(ProfileDescriptor profileDescriptor)
|
||||
public ProfileConfiguration ImportProfile(ProfileCategory category, ProfileConfigurationExportModel exportModel, bool makeUnique, bool markAsFreshImport, string? nameAffix)
|
||||
{
|
||||
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisCoreException($"Cannot find profile named: {profileDescriptor.Name} ID: {profileDescriptor.Id}");
|
||||
if (exportModel.ProfileEntity == null)
|
||||
throw new ArtemisCoreException("Cannot import a profile without any data");
|
||||
|
||||
return JsonConvert.SerializeObject(profileEntity, ExportSettings);
|
||||
}
|
||||
|
||||
public ProfileDescriptor ImportProfile(string json, ProfileModule profileModule, string nameAffix)
|
||||
{
|
||||
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(json, ExportSettings);
|
||||
if (profileEntity == null)
|
||||
throw new ArtemisCoreException("Failed to import profile but JSON.NET threw no error :(");
|
||||
// Create a copy of the entity because we'll be using it from now on
|
||||
ProfileEntity profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(
|
||||
JsonConvert.SerializeObject(exportModel.ProfileEntity, IProfileService.ExportSettings), IProfileService.ExportSettings
|
||||
)!;
|
||||
|
||||
// Assign a new GUID to make sure it is unique in case of a previous import of the same content
|
||||
profileEntity.UpdateGuid(Guid.NewGuid());
|
||||
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
|
||||
profileEntity.IsFreshImport = true;
|
||||
profileEntity.IsActive = false;
|
||||
if (makeUnique)
|
||||
profileEntity.UpdateGuid(Guid.NewGuid());
|
||||
|
||||
if (nameAffix != null)
|
||||
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
|
||||
if (markAsFreshImport)
|
||||
profileEntity.IsFreshImport = true;
|
||||
|
||||
_profileRepository.Add(profileEntity);
|
||||
return new ProfileDescriptor(profileModule, profileEntity);
|
||||
|
||||
ProfileConfiguration profileConfiguration;
|
||||
if (exportModel.ProfileConfigurationEntity != null)
|
||||
{
|
||||
// A new GUID will be given on save
|
||||
exportModel.ProfileConfigurationEntity.FileIconId = Guid.Empty;
|
||||
profileConfiguration = new ProfileConfiguration(category, exportModel.ProfileConfigurationEntity);
|
||||
if (nameAffix != null)
|
||||
profileConfiguration.Name = $"{profileConfiguration.Name} - {nameAffix}";
|
||||
}
|
||||
else
|
||||
{
|
||||
profileConfiguration = new ProfileConfiguration(category, exportModel.ProfileEntity!.Name, "Import");
|
||||
}
|
||||
|
||||
if (exportModel.ProfileImage != null)
|
||||
profileConfiguration.Icon.FileIcon = exportModel.ProfileImage;
|
||||
|
||||
profileConfiguration.Entity.ProfileId = profileEntity.Id;
|
||||
category.AddProfileConfiguration(profileConfiguration, 0);
|
||||
SaveProfileCategory(category);
|
||||
|
||||
return profileConfiguration;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AdaptProfile(Profile profile)
|
||||
{
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, MementoSettings);
|
||||
string memento = JsonConvert.SerializeObject(profile.ProfileEntity, IProfileService.MementoSettings);
|
||||
|
||||
List<ArtemisDevice> devices = _rgbService.EnabledDevices.ToList();
|
||||
foreach (Layer layer in profile.GetAllLayers())
|
||||
@@ -355,14 +530,5 @@ namespace Artemis.Core.Services
|
||||
|
||||
_profileRepository.Save(profile.ProfileEntity);
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
|
||||
{
|
||||
ActiveProfilesPopulateLeds();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,7 @@ namespace Artemis.Core.Services
|
||||
/// </summary>
|
||||
public class DataModelJsonPluginEndPoint<T> : PluginEndPoint where T : DataModel
|
||||
{
|
||||
private readonly ProfileModule<T>? _profileModule;
|
||||
private readonly Module<T>? _module;
|
||||
private readonly DataModelExpansion<T>? _dataModelExpansion;
|
||||
|
||||
internal DataModelJsonPluginEndPoint(ProfileModule<T> profileModule, string name, PluginsModule pluginsModule) : base(profileModule, name, pluginsModule)
|
||||
{
|
||||
_profileModule = profileModule ?? throw new ArgumentNullException(nameof(profileModule));
|
||||
|
||||
ThrowOnFail = true;
|
||||
Accepts = MimeType.Json;
|
||||
}
|
||||
private readonly Module<T> _module;
|
||||
|
||||
internal DataModelJsonPluginEndPoint(Module<T> module, string name, PluginsModule pluginsModule) : base(module, name, pluginsModule)
|
||||
{
|
||||
@@ -35,14 +25,6 @@ namespace Artemis.Core.Services
|
||||
Accepts = MimeType.Json;
|
||||
}
|
||||
|
||||
internal DataModelJsonPluginEndPoint(DataModelExpansion<T> dataModelExpansion, string name, PluginsModule pluginsModule) : base(dataModelExpansion, name, pluginsModule)
|
||||
{
|
||||
_dataModelExpansion = dataModelExpansion ?? throw new ArgumentNullException(nameof(dataModelExpansion));
|
||||
|
||||
ThrowOnFail = true;
|
||||
Accepts = MimeType.Json;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the end point should throw an exception if deserializing the received JSON fails.
|
||||
/// If set to <see langword="false" /> malformed JSON is silently ignored; if set to <see langword="true" /> malformed
|
||||
@@ -63,12 +45,7 @@ namespace Artemis.Core.Services
|
||||
using TextReader reader = context.OpenRequestText();
|
||||
try
|
||||
{
|
||||
if (_profileModule != null)
|
||||
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _profileModule.DataModel);
|
||||
else if (_module != null)
|
||||
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
|
||||
else
|
||||
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _dataModelExpansion!.DataModel);
|
||||
JsonConvert.PopulateObject(await reader.ReadToEndAsync(), _module.DataModel);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
|
||||
@@ -54,26 +54,6 @@ namespace Artemis.Core.Services
|
||||
/// <returns>The resulting end point</returns>
|
||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(Module<T> module, string endPointName) where T : DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided
|
||||
/// <paramref name="profileModule" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The data model type of the module</typeparam>
|
||||
/// <param name="profileModule">The module whose datamodel to apply the received JSON to</param>
|
||||
/// <param name="endPointName">The name of the end point, must be unique</param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint that directly maps received JSON to the data model of the provided
|
||||
/// <paramref name="dataModelExpansion" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The data model type of the module</typeparam>
|
||||
/// <param name="dataModelExpansion">The data model expansion whose datamodel to apply the received JSON to</param>
|
||||
/// <param name="endPointName">The name of the end point, must be unique</param>
|
||||
/// <returns>The resulting end point</returns>
|
||||
DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new endpoint for the given plugin feature receiving an a <see cref="string" />.
|
||||
/// </summary>
|
||||
|
||||
@@ -142,24 +142,6 @@ namespace Artemis.Core.Services
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(ProfileModule<T> profileModule, string endPointName) where T : DataModel
|
||||
{
|
||||
if (profileModule == null) throw new ArgumentNullException(nameof(profileModule));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
DataModelJsonPluginEndPoint<T> endPoint = new(profileModule, endPointName, PluginsModule);
|
||||
PluginsModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
public DataModelJsonPluginEndPoint<T> AddDataModelJsonEndPoint<T>(DataModelExpansion<T> dataModelExpansion, string endPointName) where T : DataModel
|
||||
{
|
||||
if (dataModelExpansion == null) throw new ArgumentNullException(nameof(dataModelExpansion));
|
||||
if (endPointName == null) throw new ArgumentNullException(nameof(endPointName));
|
||||
DataModelJsonPluginEndPoint<T> endPoint = new(dataModelExpansion, endPointName, PluginsModule);
|
||||
PluginsModule.AddPluginEndPoint(endPoint);
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
private void HandleDataModelRequest<T>(Module<T> module, T value) where T : DataModel
|
||||
{
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user